This is the multi-page printable view of this section. .
Advanced
- 1: Multi-language support
- 2: Search
- 3: Versioning
- 4: Advanced customization
- 5: Apache ECharts
- 6: Infographics with AntV
- 7: Comments with giscus
- 8: Analytics, user feedback, and SEO
- 9: Repository links and page information
- 10: AI-agent support
- 11: Print support
These capabilities extend the core authoring workflow: advanced visual customization, multilingual routing, search, version navigation, ECharts, infographics, comments, analytics, repository actions, AI-agent discovery, and print output. Enable only the features that match the site’s audience, accessibility needs, privacy boundary, and operating environment.
1 - Multi-language support
OINK uses Hugo’s multilingual page model rather than site-specific domain or
template assumptions. The included site makes English the primary language, and
Simplified Chinese (zh) the second language.
Configure languages
Define the default language and every enabled language in hugo.yaml:
weight controls both language ordering and the selector order. label is
shown in that language’s own script. locale supplies standards-friendly
language tags for HTML, alternate links, and Open Graph metadata.
Language-specific parameters override global values; other parameters inherit their global value. Put translated menus under each language when labels differ.
Organize translated content
The Oink project site colocates translations:
The base name makes the files translations of one page. Keep dates, weights, aliases, resources, and route-affecting metadata aligned unless a deliberate language-specific difference is required.
Translate all visible text, including front matter titles and descriptions, summaries, menu labels, tags, image alternatives, callouts, and shortcode arguments. Preserve commands, identifiers, configuration keys, filenames, URLs, and product names.
Sites with very large independently maintained language trees may use Hugo’s
language-specific contentDir model instead. Do not mix layouts casually: pick
one model, document it, and verify how Hugo associates translations.
Keep heading links stable
Automatic heading IDs depend on the heading text, so translated headings would normally break shared fragment links. Use the English page’s actual rendered ID as an explicit ID in the translation:
Inspect rendered HTML rather than guessing. Inline HTML, punctuation, badges, and shortcodes can affect Hugo’s generated ID. Corresponding pages should have the same heading order and rendered ID list.
Language selector behavior
The selector is generated from Hugo’s configured sites and page translations. It is hidden for a single language. With two or more languages it renders one consistent language button: a direct click advances to the next language by configured weight, while hovering for half a second or focusing the control reveals the complete language menu.
For each target language, the selector links to the current page’s translation
when it exists. If it does not exist, it links to that language’s home page
instead of producing a dead or falsely translated route. The current language
has visible and aria-current state.
SEO and document metadata
Every page emits:
- the correct HTML
langanddirvalues; - its canonical URL;
rel="alternate"links withhreflangfor configured languages;- Open Graph locale and alternate-locale metadata.
Alternate targets follow the same translated-page-or-language-home fallback as
the visible selector. Use a correct production baseURL; subpath deployments
are supported and must not be replaced by hardcoded absolute paths in layouts.
Right-to-left languages
Set direction: rtl on an RTL language:
The theme loads its committed local Bootstrap RTL artifact and uses logical CSS properties in its own shell. LTR and RTL sites use the same command:
Consumer sites do not install RTLCSS, PostCSS, or npm. Test actual RTL content, navigation, code, tables, diagrams, and mixed-direction strings rather than assuming stylesheet selection is sufficient.
UI translation bundles
Theme UI strings live in i18n/. OINK includes English, Simplified Chinese,
Traditional Chinese, and other inherited bundles. A site can override only the
strings it needs by creating its own i18n/<language>.yaml; remaining values
fall back to the theme bundle.
During translation work, run:
Contribute generally useful translations to the theme. Keep product-specific language in the site bundle.
Search by language
With offlineSearch: true, OINK generates a separate same-origin index for each
language. The Simplified Chinese index uses the theme’s CJK fallback. Search
results stay within the active language.
Verify that both offline-search-index.en.json and
offline-search-index.zh.json are generated, contain the expected pages, and
resolve under the deployed baseURL.
Translation checklist
- Every source page in the supported scope has a
.zh.mdpeer. - Front matter identity and route metadata match.
- Visible prose, UI strings, alternative text, and metadata are translated.
- Every translated Markdown heading has an explicit stable ID.
- English and Chinese rendered heading ID lists match.
- Internal links and fragments resolve in both languages.
- Navigation, breadcrumbs, previous/next links, and search stay in language.
- Dates, punctuation, spacing, and technical terminology follow the target language’s editorial conventions.
- The production build emits correct canonical and alternate metadata.
For Hugo’s underlying model, see Multilingual mode.
2 - Search
OINK’s default and recommended search is local. Hugo generates a per-language index; the theme serves Lunr and its CJK fallback from same-origin assets. The site can build and search without a public crawler, external account, CDN, or network connection.
Google Custom Search and Algolia DocSearch remain compatible online integrations. They are disabled by default and should be enabled only when the site accepts their external requests, indexing, availability, and privacy boundaries.
Only one search implementation can be active at a time.
Local search with Lunr
Enable local search in hugo.yaml:
Do not configure gcs_engine_id or params.search.algolia at the same time.
After a production build, the output contains one index per language, for
example:
The browser loads the active language’s index and displays results without leaving the page. Chinese content uses OINK’s CJK fallback instead of depending on whitespace tokenization.
Build the index before testing
Run a normal build before starting a preview:
If the server was already running when the index changed, restart it. On a
subpath deployment, confirm that the browser requests the index under the
configured baseURL rather than from the domain root.
Configure result summaries and limits
Set the summary length and maximum result count:
Choose limits that keep the search dialog responsive on mobile devices. The summary is a discovery aid, not a replacement for a well-written page description.
Exclude a page
Set exclude_search: true in page front matter:
Use this for utility, duplicate, generated, or test pages. Do not exclude a page only because its current translation is incomplete; fix the translation instead.
Style the result panel
The result panel grows with its content. A site can constrain it in
assets/scss/_styles_project.scss:
Preserve keyboard focus, visible selection, mobile width, and dark-mode contrast when overriding search styles.
Search entry points
OINK exposes search from the branded shell and can also show a sidebar input. To hide the sidebar input while retaining the main search entry, configure:
The shell’s open and close controls expose their dialog relationship and state to assistive technology. A custom implementation must preserve those semantics.
Multilingual search
Search stays in the active language. Verify that:
- every published language has its own index;
- translated titles, descriptions, and body text appear in that index;
- a result URL contains the correct language prefix;
- English results do not replace Chinese results through content fallback;
- the language selector on a result page reaches the corresponding translation or the documented language-home fallback.
For Chinese search failures, inspect the generated Chinese JSON before changing tokenization. A missing or English-only index is usually a content or build configuration problem.
Google Custom Search (optional)
Google Custom Search Engine (GCSE) searches a public site through Google’s index. It requires a deployed, crawlable production site and sends queries to a third-party service.
After creating an engine in Google Programmable Search, add a search result page:
Then configure its engine ID:
The Google search dark-mode compatibility layer is opt-in. Import it from the
consuming site’s assets/scss/_styles_project.scss when GCSE is enabled:
Create a translated result page for every supported language and use a
language-appropriate engine configuration when needed. Removing gcs_engine_id
disables GCSE.
Document the external request and privacy implications in the consuming site’s policy. GCSE is not available in an air-gapped deployment.
Algolia DocSearch (optional)
Algolia DocSearch provides a hosted crawler and interactive result panel for eligible public documentation sites. Obtain the project’s application ID, search API key, and index name, then configure:
Use a search-only public key, never an administrative key. Keep crawler rules, language facets, index updates, and external-service disclosure with the site configuration. This integration is intentionally separate from the local-first default.
The theme partials layouts/_partials/algolia/head.html and
layouts/_partials/algolia/scripts.html can be overridden for a site-specific
integration. An empty override disables that theme partial.
Custom search
If none of the supported choices fits, a site can replace the search input, result behavior, and styles. Reuse the shell’s dialog and accessibility contracts where possible. Keep custom code at the site layer unless it is provider-neutral and reusable across multiple products.
A custom online provider must be opt-in and document its network, privacy,
indexing, failure, and offline behavior. A custom local provider must publish
all runtime assets from the site or theme and respect language and baseURL
boundaries.
3 - Versioning
Depending on your project’s releases and versioning, you may want to let your users access previous versions of your documentation. How you deploy the previous versions is up to you. This page describes the Oink features that you can use to provide navigation between the various versions of your docs and to display an information banner on the archived sites.
Adding a version drop-down menu
If you add some [params.versions] in hugo.toml/hugo.yaml/hugo.json, the
Oink adds a version selector drop-down to the navbar. You specify a URL and a
name for each version you would like to add to the menu, as in the following
example:
Remember to add your current version so that users can navigate back!
The default title for the version drop-down menu is Releases. To change the
title, change the site parameter version_menu in
hugo.toml/hugo.yaml/hugo.json:
If you set the version_menu_pagelinks parameter to true, then links in the
version drop-down menu point to the current page in the other version, instead
of the main page. This can be useful if the document doesn’t change much between
the different versions. Note that if the current page doesn’t exist in the other
version, the link will be broken.
You can also configure individual menu entries:
- Use
nameinstead ofversionwhen the menu label is not a version number. - Set
nameto---to add a menu separator. - Omit
urlto render a disabled text item, such as a group heading. - Set
kindto add a kind-specific class for styling. For details, see Navigation and menus. - Set
pagelinks: falseon an entry to link to that version’s main URL even when the globalversion_menu_pagelinksparameter istrue.
For example:
To learn more about Oink menus, see Navigation and menus.
Displaying a banner on archived doc sites
If you create archived snapshots for older versions of your docs, you can add a note at the top of every page in the archived docs to let readers know that they’re seeing an unmaintained snapshot and give them a link to the latest version.
For example, see the archived docs for Kubeflow v0.6:
To add the banner to your doc site, make the following changes in your
hugo.toml/hugo.yaml/hugo.json file:
-
Set the site parameter
archived_versiontotrue:Configuration file:hugo.tomlTOMLhugo.yamlYAMLhugo.jsonJSON -
Set the site parameter
versionto the version of the archived doc set. For example, if the archived docs are for version 0.1:Configuration file:hugo.tomlTOMLhugo.yamlYAMLhugo.jsonJSON -
Make sure that site parameter
url_latest_versioncontains the URL of the website that you want to point readers to. In most cases, this should be the URL of the latest version of your docs:Configuration file:hugo.tomlTOMLhugo.yamlYAMLhugo.jsonJSON
4 - Advanced customization
OINK provides a small, layered customization contract instead of requiring a site to copy component selectors or fork the theme. Everything still passes through Hugo Extended and Hugo Pipes: no Node.js, npm, PostCSS, remote font service, or client-side preset loader is required.
This page documents the supported extension points and the first public semantic-token slice: typography. For general color-mode, code-highlighting, navbar, and template examples, see Look and feel.
Choose the right layer
Use the highest-level interface that can express the change:
| Layer | Extension point | Best for |
|---|---|---|
| Hugo configuration | hugo.yaml or page front matter |
Supported choices such as typography preset and page width |
| Sass foundation | assets/scss/_variables_project.scss |
Existing Docsy and Bootstrap variables that affect the whole bundle |
| Sass after Bootstrap | assets/scss/_variables_project_after_bs.scss |
Rare overrides that depend on Bootstrap variables or maps |
| Semantic CSS | assets/scss/_styles_project.scss |
Role tokens, a content type, one page family, or one component |
| Hugo templates | layouts/ and partial hooks |
Structural or DOM changes that CSS cannot represent |
Start with configuration or an established Sass variable. Use a semantic CSS property when a decision needs a narrower scope. Override a selector only when there is no suitable token, and override a template only when the structure itself must change.
Do not edit files inside the theme, Bootstrap, Font Awesome, or bundled font directories for site branding. Those edits are difficult to audit and will be lost or conflict during an upgrade.
How the CSS contract is layered
OINK keeps the dependency direction one-way:
Existing Docsy and Bootstrap variables remain the foundation. OINK adds a semantic role only where components need a shared meaning, such as “article body” or “technical metadata”. Bootstrap properties never point back to OINK roles, which avoids custom-property cycles.
The following API status is intentional:
- Established Docsy and Bootstrap variables remain compatible inputs where practical.
- The typography roles documented below are public site-customization APIs.
- A documented component alias, such as
--td-asciinema-font-family, has a narrower component-specific contract. - Undocumented
--td-shell-*and selector-local properties are implementation details. Do not assume every property with a--td-*prefix is public.
Typography presets
Choose a built-in site-wide preset in hugo.yaml:
| Preset | Result | Font requests |
|---|---|---|
technical |
Default OINK treatment, including Chakra Petch display text and IBM Plex Mono technical text | Uses locally bundled files only |
system |
Platform sans and monospace stacks, suitable for a neutral base or the smallest text-font footprint | Does not request OINK’s bundled text faces unless project CSS explicitly references one |
OINK writes the resolved value to data-td-typography on the <html> element.
An unsupported value stops the Hugo build instead of silently falling back. This
is a build-time site choice, not a JavaScript-powered reader preference.
The preset selects defaults; project Sass and CSS remain authoritative. For
example, a site that explicitly references IBM Plex Mono can still request it
while using the system preset.
Public font roles
Components consume roles rather than naming a brand font directly:
| CSS property | Controls | Default source |
|---|---|---|
--td-ui-font-family |
Navigation, controls, search, and general chrome | Bootstrap body font |
--td-body-font-family |
Documentation and blog prose | UI role |
--td-heading-font-family |
Article headings | $headings-font-family, or body role |
--td-code-font-family |
code, pre, kbd, samp, and terminal content |
$font-family-code |
--td-display-font-family |
Wordmarks and display titles | Chakra Petch, then UI role |
--td-meta-font-family |
Technical labels and metadata | IBM Plex Mono, then code role |
--td-print-font-family |
Printed body copy and default print headings | $td-google-font-name, then Bootstrap body font |
Change the broad semantic role in most cases. For example, Asciinema consumes
--td-asciinema-font-family, which defaults to --td-code-font-family.
Override the component alias only when terminal playback should deliberately
differ from all other code.
Reuse Docsy and Bootstrap Sass variables
OINK interprets established names instead of adding parallel Sass knobs:
| Existing variable | OINK interpretation |
|---|---|
$td-fonts-serif, $font-family-sans-serif, $font-family-base |
Bootstrap body font, then UI and body roles |
$headings-font-family |
Heading role when explicitly set |
$td-font-family-monospace, $font-family-monospace |
Bootstrap monospace foundation |
$font-family-code |
Code role and ordinary code elements |
$td-google-font-name |
Default print face |
Put these compile-time overrides in _variables_project.scss, just as in Docsy:
This changes the compiled defaults. Use semantic CSS properties in
_styles_project.scss when different areas of one site need different
treatments.
Add a site-owned font
Store reviewed .woff2 files under the consuming site’s static/webfonts/
directory, then declare and assign the face in _styles_project.scss:
For code, include an explicit CJK fallback when content can contain Chinese:
Subset and self-host fonts, include every script the site needs, use
font-display: swap, and record the license. OINK intentionally does not accept
arbitrary font URLs or CSS strings through YAML.
Scope styles by content type
Semantic properties inherit, so a content-specific treatment does not require a
second stylesheet or copied component rules. Blog pages already carry the
td-blog body class:
OINK also adds td-swagger to Swagger/OpenAPI pages. For a project-defined page
family, set body_class in front matter or a section cascade:
Use a semantic, site-owned class. Never put untrusted content in body_class.
Keep layout and typography independent
Typography, article width, and component structure are separate axes. OINK’s
existing page_width parameter supports normal, wide, and full, globally
or in page front matter:
This provides a clean foundation for distinct experiences without one all-purpose preset:
| Experience | Recommended composition |
|---|---|
| Standard documentation | page_width: normal plus the site typography preset |
| Full-width canvas | page_width: full plus a page-specific body_class if needed |
| Blog or editorial reading | td-blog scoped body and heading roles, usually with normal width |
| Code-heavy reference | A project body class that adjusts code and metadata roles |
| OpenAPI reference | The Swagger layout and td-swagger; keep renderer-specific structure in its layout |
Keeping these concerns independent prevents preset multiplication and lets a future editorial, API, or code treatment reuse the same roles.
Colors and component surfaces
Use Bootstrap variables such as $primary, $secondary, and $danger for the
compiled palette. At runtime, prefer Bootstrap semantic properties such as
--bs-body-bg, --bs-body-color, --bs-link-color, and --bs-border-color.
OINK also documents a small brand layer, including --td-brand-elev,
--td-brand-silk, --td-brand-copper, --td-brand-header-bg, and
--td-brand-mark-gradient. Override light and dark values as a pair. The
Look and feel page shows
the complete pattern.
Do not globally override a shell or component token merely because its current name looks convenient. First change its Bootstrap or documented semantic source; use a component alias only when that component must diverge.
Review checklist
Before shipping a customization:
- Build with the oldest and newest supported Hugo Extended versions.
- Check light, dark, print, forced-colors, and reduced-motion behavior.
- Review documentation, blog, code, search, and any OpenAPI pages in scope.
- Test narrow and wide viewports, including long CJK text and code lines.
- Confirm font requests are local, intentional, licensed, and no larger than necessary.
- Prefer one semantic override over repeated selector patches.
These rules preserve OINK’s central constraint: a customized site remains a single-Hugo-binary project with no additional build or runtime dependency.
5 - Apache ECharts
The echarts shortcode renders an Apache ECharts options object with the
versioned runtime bundled by Oink. Hugo parses JSON or YAML at build time,
serializes the result into the page, and loads ECharts only on pages that use
the component.
Use ECharts for quantitative charts whose axes, encodings, tooltips, or series need more control than a diagram or table provides. Keep a nearby textual summary so the conclusion does not depend on color, pointer interaction, or JavaScript.
Quick start
The example shows 12 draft pages, nine pages in review, and four pages ready to publish.
How Oink loads a chart
The shortcode creates a unique chart container and stores the parsed options in
an application/json element. The page includes the local ECharts runtime and
Oink initializer once, even when it contains several charts.
If theme is not set, Oink initializes the chart for the current site color
mode and redraws it when the reader changes modes. A ResizeObserver resizes
the chart with its container. Setting an explicit ECharts theme opts out of
automatic site-theme switching for that chart.
Shortcode parameters
| Parameter | Default | Behavior |
|---|---|---|
height |
400px |
Accepts a nonnegative number with px, rem, em, vh, vw, or % |
theme |
unset | Uses a named ECharts theme; when unset, follows the site’s light or dark color mode |
full |
false |
Set to true to remove Oink’s normal content-width clamp |
Invalid height values fail the Hugo build. The shortcode body must decode to an ECharts options object; malformed JSON or YAML also fails at build time instead of creating a blank chart silently.
Choose a guide
- Chart gallery demonstrates datasets, bars, lines, areas, pies, scatter plots, legends, and visual encodings.
- Callbacks and trusted code explains formatter functions,
data-dependent styles, the
$fn:namebridge, and its security boundary.
Start with declarative JSON or YAML. Add JavaScript callbacks only when the ECharts option cannot be expressed as data.
Authoring checklist
- State the chart’s conclusion and data scope in prose.
- Label axes, units, series, and time ranges explicitly.
- Do not use color as the only way to distinguish important values.
- Keep legends and tooltips readable in both site color modes.
- Test the chart at narrow widths and with long translated labels.
- Prefer a shared
datasetwhen several series use the same records. - Record the data source and observation date for nonillustrative data.
- Avoid animation when it does not help comprehension, and respect reduced motion for custom effects.
Further reference
OINK documents its wrapper and delivery behavior; the full options schema
belongs to Apache ECharts. Use the
ECharts concepts handbook,
dataset guide, and
option reference for chart-specific
settings. The theme’s VENDOR.json records the exact runtime version and
license shipped by a release.
5.1 - ECharts gallery
These examples use only structured YAML. They require no callback code and therefore stay within the simplest ECharts authoring and review boundary. The numbers are illustrative.
Reuse a dataset
ECharts dataset keeps records separate from their visual encoding. Series can
refer to dimensions by name, which is easier to review than repeating parallel
arrays.
Bar chart from a dataset
The example shows the median duration falling from 18 minutes for drafting to four minutes for publication.
Line and area comparison
Use a shared category axis when several series describe the same intervals. The area fill emphasizes volume; the lines preserve the individual trends.
Both language queues reach 13 reviewed pages on Friday; the Chinese queue catches up after starting one page lower.
Donut breakdown
A donut works for a small part-to-whole comparison. Keep the categories few, show labels directly, and provide the totals in text.
The 64-page set contains 28 guides, 17 reference pages, 11 tutorials, and eight concept pages.
Scatter plot with visual encoding
visualMap can encode a third dimension without callback code. The following
plot maps build size to point size and build status to color.
Larger sites take longer to build in this illustrative sample; point size and color both encode the third value so color is not the only cue.
Production notes
Keep example data close to the chart only when it is small and editorial. For larger or generated datasets, produce the options during the site’s content pipeline and review the resulting page source. Oink does not fetch chart data from a remote endpoint automatically; adding a network request is an explicit site integration and changes the local-first and privacy boundary.
5.2 - ECharts callbacks and trusted code
Most ECharts options should remain declarative JSON or YAML. Some valid options,
including custom formatters and data-dependent styles, require functions. Oink
supports those cases through fenced JavaScript blocks and $fn:name references.
Trusted-author boundary
Callback code runs in every visitor’s browser with the page’s origin and normal JavaScript privileges. Oink safely serializes structured chart options, but it does not sandbox author-supplied callbacks. Only trusted project authors should add or review them.
Callbacks can also change a site’s Content Security Policy requirements because the shortcode emits an inline registration script. Prefer declarative options when they can express the same behavior.
Register and reference functions
Place one or more js or javascript fences inside the shortcode. Declare each
function with a named var, let, const, or function declaration, then refer
to it from YAML or JSON as $fn:name.
Oink removes the JavaScript fences before parsing the remaining options,
registers the named functions, and replaces $fn:name values before calling
chart.setOption().
Example: labels and colors
The following chart formats duration labels and highlights the slowest stage. Its data says writing takes 18 minutes, review takes 11, and publication takes four.
Callback checklist
- Keep functions deterministic and limited to chart presentation.
- Do not read cookies, credentials, storage, or unrelated page content.
- Do not fetch remote data from a formatter or style callback.
- Use a unique, descriptive function name on pages with several charts.
- Treat code copied from an external example as source code that requires review and license checking.
- Exercise callbacks with missing, null, string, and numeric values as appropriate.
- Test both site color modes, narrow layouts, printing, and reduced motion.
Troubleshooting
If a $fn:name value remains unresolved, verify that the spelling matches a
named declaration inside the same page and that the fence language is js or
javascript. Anonymous expressions that are not assigned to a name cannot be
registered.
If Hugo fails before rendering, reduce the body to valid JSON or YAML first, then add one callback. A browser console error means the structured options parsed successfully but callback execution or an ECharts option still needs inspection.
6 - Infographics with AntV
The infographic shortcode renders the AntV Infographic DSL with the versioned
runtime bundled by Oink. Use it for processes, timelines, cycles, funnels,
roadmaps, and compact visual summaries where a statistical chart would be too
literal.
The DSL is serialized as data, not inserted as arbitrary HTML or executable code. The browser runtime turns it into SVG and loads only on pages that use the shortcode.
Quick start
The same three steps appear below. Drafting creates the first version, review checks facts and language, and publication builds and verifies the site.
Syntax anatomy
An infographic normally contains:
infographic TEMPLATE, which selects a built-in AntV template;- a
datablock with an optionaltitleanddesc; - an
itemslist withlabel,desc, optionalvalue, and optional nestedchildrenfields; - an optional
themeblock for a built-in theme or explicit colors.
Indentation defines structure. Keep labels short, use descriptions for context, and choose a template whose visual relationship matches the prose. A decorative sequence is not a substitute for an actual hierarchy or comparison.
Shortcode parameters
| Parameter | Default | Behavior |
|---|---|---|
height |
auto |
Accepts auto or a nonnegative number with px, rem, em, vh, vw, or % |
full |
false |
Set to true to remove Oink’s normal content-width clamp |
Invalid height values and an empty DSL body fail the Hugo build. DSL schema or template errors are reported by the browser runtime in the infographic container.
AntV themes belong to the DSL rather than the shortcode parameters. They do not automatically follow Oink’s site color mode, so verify foreground, background, and surrounding-page contrast in both modes.
Choose a guide
- Processes, timelines, and cycles demonstrates three common ways to explain a sequence.
- Layouts, funnels, and themes demonstrates grids, narrowing stages, template selection, and a built-in hand-drawn theme.
The AntV package contains many templates. Start with the smallest visual form that clarifies the relationship, not the most decorative form available.
Authoring and accessibility
- Summarize the same conclusion in ordinary text before or after the graphic.
- Keep the reading order meaningful and labels concise.
- Do not use color or shape as the only carrier of status.
- Check long translated labels, narrow screens, printing, and both site color modes.
- Avoid remote image or icon identifiers in a local-first page unless their network and license boundary has been reviewed explicitly.
- Record the source and date when values are not illustrative.
SVG improves visual fidelity, but it does not guarantee that every template exposes the same semantic structure as native headings, lists, and tables. Essential instructions must remain available in adjacent prose.
Further reference
OINK documents its shortcode and delivery boundary. For the full DSL, template
gallery, and theme model, use the
AntV Infographic documentation,
gallery, and
source repository. The Oink theme’s
VENDOR.json records the exact bundled version, checksum, and MIT license file.
6.1 - Processes, timelines, and cycles
Sequence templates answer different questions. A horizontal process emphasizes ordered handoffs, a timeline emphasizes chronology, and a cycle emphasizes that the last stage feeds the first again. The surrounding prose must state which relationship matters.
Horizontal process
Use list-row-simple-horizontal-arrow for a short left-to-right sequence. On
narrow screens, keep labels brief and verify that the rendered order remains
clear.
The process moves from planning through writing and review to a separately verified hosted result.
Chronological timeline
Use sequence-timeline-simple when time or release order is the primary
relationship.
The timeline separates four evidence points; a passing test does not skip the public-tag or deployment stages.
Continuous cycle
Use sequence-circular-simple only when the final item genuinely returns work
to the first. Do not use a cycle for a process that has a terminal state.
Verification produces new observations, so the maintenance loop returns to its first stage.
Selection rule
If removing the arrows or time axis would not change the meaning, use a native list or cards instead. Infographics should reveal a relationship, not decorate an otherwise unrelated set of statements.
6.2 - Infographic layouts, funnels, and themes
AntV templates combine a structure with item and title treatments. Changing the
template changes the implied relationship, so review meaning before appearance.
The examples below use flat items data and no remote icons.
Grid of grouped facts
Use list-grid-badge-card for peer facts that share one topic but have no
required order.
The four gates are peers. None should be drawn as a prerequisite for another.
Narrowing funnel
Use sequence-funnel-simple when each stage intentionally reduces a population.
Include value fields and repeat the numbers in prose.
Forty drafted pages become 34 fact-checked pages, 31 language-reviewed pages, and 28 verified published pages.
Built-in hand-drawn theme
Themes change styling, not data meaning. The hand-drawn theme is useful for
informal planning material; a custom primary color can still align it with the
site.
Choose a template family
| Relationship | Useful starting templates |
|---|---|
| Ordered handoff | list-row-simple-horizontal-arrow, sequence-steps-simple |
| Chronology or roadmap | sequence-timeline-simple, sequence-roadmap-vertical-simple |
| Repeating loop | sequence-circular-simple, sequence-circle-arrows-indexed-card |
| Peer facts | list-grid-badge-card, list-grid-compact-card |
| Progressive reduction | sequence-funnel-simple, sequence-pyramid-simple |
| Hierarchy | hierarchy-tree-*, hierarchy-mindmap-* |
Template availability belongs to the bundled AntV version. Before adopting a
less common template, render it with realistic English and Chinese content and
pin the Oink release whose VENDOR.json provides it.
Layout checklist
- Keep peer labels grammatically parallel.
- Use
valueonly when it has a defined unit or meaning. - Avoid a fixed height that clips translated text.
- Use
full=trueonly when the surrounding page and print layout need it. - Verify template meaning, contrast, overflow, and reading order separately.
- Keep remote icon and image references out of network-isolated documentation.
7 - Comments with giscus
OINK supports giscus through a Hextra-compatible comments configuration
under params.comments. giscus gives each content page a comment thread backed
by GitHub Discussions and lets readers comment through GitHub OAuth.
How giscus works
When a page loads, giscus searches the configured repository for a Discussion that matches the page. If it does not find one, the giscus bot creates it when a reader submits the first comment or reaction. Maintainers moderate comments in GitHub Discussions.
Anyone can read a public thread. To comment, a reader selects Sign in with GitHub and authorizes the giscus app to post on their behalf. OINK never asks for or stores the reader’s GitHub password or access token.
giscus is an opt-in online integration. Active comment pages load a script and
iframe from https://giscus.app; they do not work in a network-isolated
runtime. Document this external boundary in the consuming site’s privacy and
security policy when required.
Prepare GitHub
Before configuring OINK:
- Use a public GitHub repository for the comment threads. Visitors cannot read Discussions in a private repository.
- Enable GitHub Discussions under the repository’s Settings > Features.
- Install the giscus GitHub App for that repository. Without the app, visitors cannot comment or react.
- Choose a Discussion category. giscus recommends an Announcements category so that only maintainers and the giscus bot can create new Discussions.
The repository ID and category ID are public identifiers, not credentials. Do not add a GitHub personal access token, OAuth secret, or password to Hugo configuration.
Generate repository settings
Open giscus.app and complete its configuration form:
- Select the interface language.
- Enter the repository as
OWNER/REPOSITORYand wait for the validation to succeed. - Select the page-to-Discussion mapping.
pathnameis OINK’s default. - Choose the Discussion category and optional features.
- Locate the generated
<script>block.
Copy these generated values into OINK configuration:
| Generated attribute | OINK key |
|---|---|
data-repo |
repo |
data-repo-id |
repoId |
data-category |
category |
data-category-id |
categoryId |
Choose a stable mapping
The mapping determines which Discussion belongs to each page. pathname is a
good default when published paths are stable and the same repository serves
multiple domains or preview environments.
Changing mapping, moving a page, or changing its permanent URL can make giscus
look for a different Discussion. Choose the mapping before collecting comments
and preserve redirects or Discussion titles during a migration. Enable strict
matching when similar page paths could otherwise select the wrong thread.
Enable comments site-wide
Add the generated identifiers to the consuming site’s hugo.yml and set
enable: true:
Replace all uppercase placeholders with the exact values generated by
giscus.app. OINK requires repo, repoId, category, and categoryId before
it renders giscus. Missing or blank required values produce a Hugo warning and
skip giscus instead of failing the build.
Configuration reference
| Key | Default | Purpose |
|---|---|---|
enable |
false |
Enables the configured comment provider globally. |
type |
giscus |
Selects giscus. Other provider names are not supported. |
repo |
— | Public repository in OWNER/REPOSITORY form. |
repoId |
— | Repository node ID generated by giscus.app. |
category |
— | GitHub Discussions category name. |
categoryId |
— | Category node ID generated by giscus.app. |
mapping |
pathname |
Maps the current page to a Discussion. |
term |
— | Supplies the term required by mappings such as specific or number. |
strict |
0 |
Uses strict Discussion-title matching when set to 1. |
reactionsEnabled |
1 |
Shows reactions for the Discussion’s main post. |
emitMetadata |
0 |
Sends Discussion metadata messages to the parent page. |
inputPosition |
top |
Places the comment editor at top or bottom. |
theme |
auto |
Follows the OINK theme, or selects a built-in/custom giscus theme. |
lang |
Page language | Overrides the automatically selected giscus interface language. |
loading |
lazy |
Defers iframe loading until the reader approaches the comments. |
ariaLabel |
Comments |
Labels the comments region for assistive technology. |
errorMessage |
Load-error text | Replaces the message shown when giscus cannot load. |
Boolean-like feature values accept YAML booleans or giscus-style 0 and 1
values.
Locale, theme, and accessible text
OINK selects the giscus locale from the active Hugo language. Simplified,
Traditional, and Hong Kong Chinese map to the corresponding giscus locales;
unsupported languages fall back to English. Set lang only when the automatic
choice is not appropriate.
With theme: auto, the iframe follows OINK’s light/dark selector and the
browser’s preferred color scheme. A built-in giscus theme name or custom theme
URL disables that automatic switch.
For a multilingual site, localize the comments-region label and load-error text under each language’s parameters. Language parameters merge with the global repository settings:
Override one page
The comments front matter field overrides the global switch in either
direction.
Enable one page
Keep the complete repository configuration in hugo.yml, leave the global
switch off, and opt in selected pages:
Disable one page
When comments are enabled globally, opt out pages that should remain static:
An explicit comments: false suppresses both giscus and legacy Disqus on that
page.
Coexist with Disqus
OINK keeps existing Hugo Disqus configuration compatible during migration. When valid giscus configuration is active for a page, OINK suppresses Disqus so that only one comment system renders. If giscus is enabled but its required settings are incomplete, OINK warns, skips giscus, and can leave configured Disqus as a fallback.
Remove the Disqus service configuration after the migration is complete and every intended page uses giscus.
Content Security Policy
A strict Content Security Policy must permit giscus in both script-src and
frame-src. Merge these sources into the site’s existing policy instead of
replacing its other directives:
OINK’s initializer remains a same-origin bundled asset and is included only on
pages where giscus is active. If the external script fails or does not create an
iframe, OINK clears the loading state and exposes errorMessage in a live
status region.
Verify the integration
-
Build the site and confirm there is no missing-key warning:
BASH -
Start a local preview and open a page where comments should be active:
BASH -
Confirm that the giscus iframe shows Sign in with GitHub and uses the active page language.
-
Toggle OINK between light and dark themes and confirm that the comment widget follows it when
theme: auto. -
Open a page with
comments: falseand confirm that it has no giscus or Disqus widget. -
Submit one test comment, then confirm that the expected Discussion appears in the configured category and can be moderated on GitHub.
A browser-console message saying that the Discussion was not found is expected before the first comment or reaction creates it.
Troubleshooting
- The build warns about missing keys: regenerate the configuration at giscus.app and copy all four required identifiers without renaming them.
- The widget does not appear: check
params.comments.enable,params.comments.type, the page’scommentsfront matter, and Hugo’s warning output. - GitHub sign-in or posting fails: confirm that the repository is public, Discussions are enabled, and the giscus GitHub App is installed for the repository.
- The browser blocks giscus: inspect the console and response headers, then
allow
https://giscus.appin the applicable CSP directives. - An existing thread is not found: restore the original mapping and page path, or rename/migrate the Discussion deliberately before changing the URL.
- The interface language is wrong: verify the Hugo language name and locale,
or set
params.comments.giscus.langexplicitly.
8 - Analytics, user feedback, and SEO
OINK does not contact analytics, form, comment, or advertising services by default. These integrations are site decisions: enable them explicitly, document the data boundary, and provide any consent or policy required by the site’s users and jurisdiction.
Adding analytics
Hugo provides embedded templates for analytics services. When a site configures Google Analytics, browser usage information such as page views and custom events is sent to Google. This is incompatible with a fully air-gapped runtime and may be incompatible with a strict same-origin Content Security Policy.
Setup
Obtain a Google Analytics measurement ID for the site, then use Hugo’s current service configuration:
Do not also set the deprecated top-level googleAnalytics key. Analytics are
normally emitted only for a production Hugo environment. Build a production
preview and inspect its HTML and browser network log before publication.
If analytics is disabled, OINK emits no Google Analytics request. Remove the configuration entirely rather than inserting a fake identifier.
User feedback
OINK can show a “Was this page helpful?” widget at the bottom of documentation pages. The widget presents Yes and No actions and then displays a configured response, usually with a link to open a documentation issue.
The response can remain useful without analytics: it can direct the reader to an issue template, discussion, email address, or another site-owned feedback channel. Collection and event reporting happen only when the site configures an appropriate destination.
How feedback data is useful
Combine feedback with context instead of treating one score as proof. Pages with high traffic and repeated negative feedback are useful review candidates; highly rated pages can reveal patterns worth testing elsewhere.
Make focused editorial changes when possible. For example, update one stale tutorial, or move a code example earlier on a small group of pages, then compare feedback over an appropriate period. Record releases, traffic shifts, support events, and other factors that could explain the change.
Feedback is directional evidence, not a substitute for user research, accessibility review, support data, or technical validation.
Setup
OINK keeps the widget off by default. Set the global default and configure localized responses. For English:
For Simplified Chinese, put translated strings in languages.zh.params:
Visible response HTML is trusted site configuration. Keep it small, review its links, and do not interpolate untrusted values.
When Google Analytics is configured, the widget can emit a custom page_helpful
event. A positive action uses params.ui.feedback.max_value (100 by default); a
negative action uses 0.
Access feedback data
For Google Analytics, inspect the page_helpful event in the provider’s events
report and create a page-level report when needed. An absent event may mean no
interaction occurred, analytics was blocked or disabled, consent was not given,
or the selected time range is wrong.
Do not enable analytics solely to make the widget visible. A site can keep the response-and-link experience while leaving event collection disabled.
Override feedback on one page
Set feedback in page front matter. The page value overrides the global default
in either direction:
Use feedback: false to hide the widget on a page when the global default is
enabled. For compatibility, hide_feedback: true also hides it when feedback
is not set.
Set the default for all pages
Set the site parameter. OINK defaults it to false; set it to true only when
most documentation pages should show the widget:
Add a contact form with Fabform
Fabform and similar hosted form endpoints are optional online services. After creating an account and reviewing its data handling, a site can post a form to its assigned endpoint:
Replace {form-id}, translate the visible labels, add a privacy notice, and
provide error and success states. The form will not work offline. A local or
first-party endpoint is preferable when the site must keep submissions within
its own boundary.
Search engine optimization metadata
For each page, OINK chooses the HTML meta description from the first available value:
- the page’s
descriptionfront matter field; - Hugo’s computed page summary for non-index pages;
- the site description in
params.
Write a concise, page-specific description in every language. Do not copy the English description into a Chinese page. Search metadata cannot compensate for thin, duplicated, or inaccurate content.
The theme also emits canonical and alternate-language links from Hugo’s page
translations. Use a correct production baseURL, stable translated routes, and
explicit translated heading IDs. Add other meta tags through the site’s
layouts/_partials/hooks/head-end.html override only when they are not already
provided by the theme.
See Hugo’s Google Analytics configuration, page summaries, and Google’s SEO starter guide for the underlying service and content concepts.
9 - Repository links and page information
OINK’s documentation and blog layouts can show links to the current page’s source repository:
- View page source opens the source file.
- Edit this page opens an editable source view.
- Create child page starts a new file below the current page and can use the
site’s
assets/stubs/new-page-template.mdtemplate. - Create documentation issue opens an issue against the documentation repository with page context.
- Create project issue optionally targets a separate product repository.
The built-in URL patterns target GitHub-style repositories. Verify every action when using another compatible host, and override the relevant partial for a different URL scheme.
Link configuration
A typical site configuration is:
The values can be set globally, per language, in a section cascade, or in page front matter when content comes from more than one repository.
github_repo
The documentation source repository URL. It drives view, edit, child-page, and documentation-issue links:
Omit it to suppress repository-derived page actions. Do not point it at the theme repository when the page source actually lives in a consuming site.
github_subdir (optional)
Set the path from the repository root to the Hugo site source. This project
stores its site in oink.pgsty.com:
The value is a repository path, not a local absolute path and not the content directory itself unless that is the actual site root.
github_project_repo (optional)
Set a separate product repository to show Create project issue:
Use the documentation repository for content defects and the product repository for behavior discussed by the page. If that distinction is not clear to readers, omit the second link.
github_branch (optional)
Set the branch used by source and edit URLs:
This is normally the site’s source branch. It is not necessarily the deployed branch, generated Pages branch, or theme revision.
path_base_for_github_subdir (optional)
Use a section cascade when a subtree is mounted from another repository. The
path base is removed before the remaining content path is appended to
github_subdir:
For a source page at content/reference/api/client.md, this configuration maps
the repository path to docs/api/client.md.
path_base_for_github_subdir can be a regular expression. A language-directory
site might use:
OINK’s colocated .md / .zh.md layout normally uses the same static base for
both languages and does not need the language component in this expression.
When the source file has another name, use a from and to mapping. This
example maps a section _index.md to an upstream README.md:
Test view and edit links from a leaf page, a section page, and both language versions. A regular expression that removes too much can produce a plausible but incorrect repository URL.
github_url (optional)
github_url is deprecated. Use
path_base_for_github_subdir and the
repository parameters for new content.
A legacy page can set a complete custom edit URL in front matter:
Pages using this value expose only Edit this page. A site-specific template override is preferable when the destination is not GitHub-compatible.
Disabling links
Each action has a stable CSS class:
| Link | Class |
|---|---|
| View page source | .td-page-meta__view |
| Edit this page | .td-page-meta__edit |
| Create child page | .td-page-meta__child |
| Create documentation issue | .td-page-meta__issue |
| Create project issue | .td-page-meta__project-issue |
Hide an action in assets/scss/_styles_project.scss when the destination does
not support it:
Prefer omitting an unavailable global destination in configuration. CSS hiding is useful for selective policy; it does not make a malformed link correct.
Last-modified page metadata
Enable Hugo Git information and configure the source repository:
OINK can then show the last commit date, subject, hash, and source link on documentation and blog pages. CI must fetch enough Git history for the current file; shallow checkouts can produce missing or misleading metadata.
To hide the note for a particular site or section, override its style or the responsible page-meta partial. Do not label a file “last modified” from the build timestamp when Git history is unavailable.
10 - AI-agent support
Features described in this page are experimental, and are useful for early adoption and evaluation. Output details and validation coverage may change in future releases. To track the phased evolution of the agent-support feature, see Improve support for AI-agent doc consumption #2614.
Features
When your site opts in, these are the user-facing and machine-readable behaviors Oink enables:
- Markdown output format support. Your project’s
outputsconfiguration controls which page kinds publish Markdown. - Discovery: page HTML headers include
rel="alternate"links to the Markdown version of the page. - View Markdown: page meta area includes a View Markdown link to the Markdown version of the page.
- Open in ChatGPT / Claude: documentation page actions can hand the current browser URL to either assistant. Oink builds the prompt only when the reader activates the link, preserving the deployed host, query string, and fragment.
llms.txt: site-root file listing.
The remainder of this page explains how to enable each feature, and discusses validation and metrics supported with examples.
Enable Markdown output
Hugo comes with several built-in output formats, including markdown. To
enable Markdown output, add markdown to the Hugo outputs configuration for
the page kinds you want to support. For example:
Opt pages out
By default, Hugo’s outputs map (whether in multi-file site config or page
front matter) is a full replacement for each page kind, not a merge 1.
When you add markdown, keep every format your site already relies on – for
example RSS and print on sections as is shown in the examples above.
To opt pages out of Markdown output, set outputs in page front matter to
HTML only, or whatever your page’s default output formats are while excluding
markdown. For example:
Enable llms.txt
The llms.txt format is a simple text format for listing machine-readable links
to site content. It is designed to be easy for agents to discover and parse, and
to complement the richer but more complex Markdown outputs. To learn more, see
llmstxt.org.
Oink generates llms.txt at the site root, and includes links to the home page,
main menu pages, and Markdown alternates where they exist. To enable it, add
LLMS to the Hugo outputs configuration for the home page. For example:
For an example of the generated llms.txt for this site, see
/llms.txt.
Customize output
Oink renders Markdown output via layouts/all.html and generates llms.txt
via layouts/index.llms.txt. You can override these defaults at several levels:
- Per kind — Add templates such as
home.mdor_default/single.mdunderlayouts/in your project to tailor Markdown output for specific Hugo kinds. - Per shortcode — Add output-format-specific shortcode templates to project-local shortcodes so they emit Markdown-friendly content when appropriate.
- Per page — Provide page-specific content or structure for high-value pages that need a curated agent-facing view.
Server-side support
While outside the scope of the theme, sites can facilitate agent discovery and
access to Markdown content by implementing server-side content negotiation. For
example, honoring Accept: text/markdown on the same URL as HTML.
Validation and metrics
We use AFDocs to assess basic structural support for agent-facing content,
and to validate that generated outputs meet the configured checks. We also
encourage sites to implement their own monitoring and metrics on agent access
patterns—for example logging requests to Markdown URLs or llms.txt, and
collecting metrics on their use. For details, see
Agent-support checks.
The oink.pgsty.com project contains AFDocs configuration and npm scripts
so maintainers can score a deployed URL against checks that overlap with Oink’s
agent-support goals, including Markdown URLs, llms.txt, and related categories.
Scorecard examples
For scorecard examples, see:
-
OpenTelemetry agent score online report
-
An AFDocs scorecard for this site:
oink.pgsty.comscorecardTEXT
For details on how these checks are configured, see Agent-support checks.
-
This is contrary to the documented Hugo behavior for front-matter configuration, but it is confirmed with our testing as of Hugo 0.158.0. ↩︎
11 - Print support
Individual documentation pages print well from most browsers as the layouts have been styled to omit navigational chrome from the printed output.
On some sites, it can be useful to enable a “print entire section” feature (as seen in this user guide). Selecting this option renders the entire current top-level section (such as Advanced for this page) with all of its child pages and sections in a format suited to printing, complete with a table of contents for the section.
To enable this feature, add the “print” output format in your site’s
hugo.toml/hugo.yaml/hugo.json file for the “section” type:
The site should then show a “Print entire section” link in the right hand navigation.
Further Customization
Disabling the ToC
To disable showing the table of contents in the printable view, set the
disable_toc param to true, either in the page front matter, or in
hugo.toml/hugo.yaml/hugo.json:
Layout hooks
A number of layout partials and hooks are defined that can be used to customize
the printed format. These can be found in layouts/_partials/print.
Hooks can be defined on a per-type basis. For example, you may want to customize
the layouts of heading for “blog” pages vs “docs”. This can be achieved by
creating layouts/_partials/print/page-heading-<type>.html such as
page-heading-blog.html. It defaults to using the page title and description as
a heading.
Similarly, the formatting for each page can be customized by creating
layouts/_partials/print/content-<type>.html.