Component contract
This contract describes OINK v1.3.1. Theme development uses the sibling checkout; release acceptance verifies the published module with replacements and workspaces disabled. Consumer adoption and deployment have their own checks.
Tutorials and exhaustive examples belong in the reader-facing Components section. This page defines the API and behavior that those guides rely on.
Authoring model
Use ordinary Markdown when one block plus attributes can express a component. Use shortcodes for compound bodies or facts Markdown cannot carry. There is no parallel component registry. Native forms require:
Only {{% steps %}} uses percent delimiters because its body belongs to the
page outline; every other shortcode uses angle delimiters. Compound bodies pass
through content/render-block.html with a unique ID scope. Shortcode and
component parameter captions, labels, titles, and names are plain text;
Markdown belongs in bodies. Landing narrative fields follow their own contract.
An icon is one Font Awesome class pair. Components expose safe classes and
attributes, not arbitrary color or inline style.
Public API
OINK has 29 shortcodes:
- core:
tabs,tab,steps,cards,card,fields,field,include,kbd,badge,param,comment,contributors,asciinema; - Book:
fig,tbl,eq,eg,xref,book-toc,book-figures,book-tables,book-equations,book-examples; - release:
release-card,release-assets,download; - OpenAPI:
swagger,redoc.
| Component | Native form | Shortcode form | HTML runtime |
|---|---|---|---|
| Callout | > [!TYPE], fold, {icon=} |
none | none |
| Tabs | adjacent fences/tables with {tab= group= value=} |
tabs / tab |
tabs on used pages |
| Steps | ordered list + {.steps} |
steps |
none |
| Cards | link list + {.cards} |
cards / card |
none |
| Fields | table + {.fields} |
fields / field |
none |
| FileTree | filetree data fence |
none | divider only with comments |
| Gallery | gallery data fence |
none | shared Image Zoom when eligible |
| Image | Markdown image + block attributes | none | Image Zoom when eligible |
| Table | attributes, caption, number, or tabs | tbl for compound Book tables |
tabs when tabbed |
| Book target | image/table/passthrough/fence + {num=} |
fig, tbl, eq, eg |
none |
| Release assets | checksums data fence |
release-assets |
copy in HTML |
| Math and chemistry | passthrough, math, chem fences |
eq |
none; build-time rendering and local styles |
| Diagram/data | mermaid, plantuml, markmap, echarts, infographic fences |
none | selected local runtime only |
Validation
Invalid author input follows the architecture contract:
warn, use the documented safe fallback or omit the component, and let
--panicOnWarning make the same diagnostic fatal at publication gates. Named
and positional forms are not mixed. Book target IDs match
[A-Za-z][A-Za-z0-9_.:-]*; Book numbers match [0-9A-Za-z.-]+; classes are
token-validated. Hook and shortcode targets share one page registry, so
collisions cannot produce duplicate output IDs.
URLs use content/url.html; raw backslashes are invalid because browsers may
interpret them as URL separators. Images resolve through page resources, section
resources, global assets, then static or explicit remote URLs. Local rasters
carry intrinsic dimensions; SVG, static, and remote sources remain valid but
cannot use Hugo image operations.
Resource metadata alt must be a string; an invalid value warns and is ignored,
preserving the image’s authored alt text.
Empty tabs warn and emit no container in HTML, Print, Markdown, or RSS.
An xref without a usable target or with a missing target page preserves its
text without a link. If fig supplies both caption and title, the warning
and all outputs prefer caption.
Generic attributes on block mathematics do not enable Book numbering. Without
num, id and caption warn and are ignored; the formula stays unnumbered.
Component behavior
Callouts and tabs
Callout types are note, tip, important, warning, caution, success, danger, question, example, quote, and details.
- makes the callout initially collapsed. + makes it expanded.
Unknown types render as plain blockquotes, with their markers preserved and no JS.
Adjacent tabs group only when consecutive and of the same block kind.
group enables the hash #<group>-<value> and storage td-tabs:v1:<group>. Ungrouped tabs use neither.
HTML exposes all panels before JS. Print expands them. Markdown keeps authored source. RSS provides the rendered text summary.
Grouped tab IDs and local links remain unchanged in HTML and single-page Print.
Repeated groups and nested tab containers keep unique IDs within each page.
Multi-page section and whole-Book Print prefix generated tab container and panel IDs with the source page namespace and update their local links. Reusing a group across chapters must not send a tab link to another chapter. Cross-page links to ordinary HTML keep their destination.
Fragment rewriting accepts normal HTML attribute syntax and preserves text inside other attributes.
The full form supports arbitrary Markdown. tab.label is required. value is required exactly when the parent has group.
Orphan tab shortcodes warn and render nothing.
Steps, cards, fields, and tables
Native steps accept ordinary block content. Use the shortcode only when a step
must contain a percent-delimited container. Native cards are link lists; the
full form adds bodies, badges, icons, and images. Native fields map the first
column to the name, the last to the description, and middle columns through
meta= or headings; the full form allows block descriptions. card and
field are valid only inside their parents.
Field anchors are field-<name> with lowercase punctuation runs collapsed to
hyphens, so params.ui.typography becomes field-params-ui-typography.
Duplicate anchors receive positional suffixes.
The table hook owns responsive wrapping and captions. .matrix makes the first
column row headers; .full-width widens the outer wrapper to the article width.
On screens, .td-table--wide opts technical tables into intrinsic sizing inside
that scrollable wrapper, preserving identifiers with overflow-wrap: break-word
and word-break: normal. Body cells have a soft max-width: 40ch bound so prose
wraps. Ordinary tables retain their existing layout. The modifier has no effect
on .fields, which renders a definition list, or on static and print output.
.fields cannot combine with matrix, full-width, numbering, or tabs; numbering
and tabs are also mutually exclusive. This width modifier is available in OINK 1.3.1.
Images, Gallery, FileTree, and fences
The Markdown image hook is the ordinary image API. Inline images stay inline;
block images become figures with caption or num. Image processing belongs
to this native form alone: the full fig source form is a numbered container
whose parameter list deliberately excludes command/options, so a processed
numbered image is written as a native block image with num. Allowed image attributes
are id, num, caption, width, height, link, command, and options
plus shared safe attributes. command and options appear together and use
Hugo Fit, Resize, Fill, or Crop on processable local resources. A plain
linked image uses Markdown syntax; the link attribute therefore requires a
caption or number. Linked and decorative images do not load Zoom.
Zoom triggers keep the image’s alt text and localized preview action in their ARIA accessible name, without inserting helper text into the article. Copying content as plain text or rich HTML must not add preview instructions, even when an editor discards the theme’s styles. Authored images and captions are preserved. When Draw.io and Image Zoom share an image, Edit and Zoom remain separate sibling buttons. The editor entry supports keyboard access and stays visible on touch devices and in forced-colors mode. The Draw.io editor opens in a modal dialog with a localized host Close button. The button, backdrop, editor exit message, and Escape while focus is in the host dialog dismiss it, release its message listener, and restore focus to Edit. Cross-origin iframe keystrokes belong to the editor and cannot bubble to the host; users can leave the editor’s Tab sequence to reach the host Close button. Opening another editor starts a fresh session.
Gallery accepts one Markdown image per line with optional description, link,
and class. FileTree accepts indentation, - name, optional /, comments, and
validated icon/tone/open/type attributes. FileTree fence wrap accepts true
or false, defaults to false, and changes only screen comment layout: true
wraps comments, including unbroken tokens, while rows grow from their existing
minimum height. Filenames retain ellipsis. Missing or false values preserve
compact desktop/tablet rows; mobile stacking, divider controls, directory
folding, and expanded Print output keep their existing behavior. Invalid values
warn and fall back to false; strict publishing rejects the warning. Markdown
preserves explicit wrap values and tree source; Print renders expanded static
figures and trees.
All code highlighting uses Chroma. Common fence attributes include title,
copy, wrap, collapse, label, id, line options, tabs, and Book
num/caption. Copy returns authored source. Mermaid palettes follow mode independently
of visual presets. The default dark edge-label background is #404040 for
AA text contrast; authored params.mermaid.themeVariables remain authoritative.
ECharts input is declarative
JSON/YAML; callbacks use $fn:<name> from window.OinkEchartsFunctions, never
embedded script execution.
Markmap node text on screens follows the article text color (--bs-body-color)
via --markmap-text-color, dynamically adapting to light/dark mode changes,
style presets, and restored user preferences to maintain contrast. Printing
retains Markmap’s default dark text on the white canvas. This mapping is scoped
to article content across both the wrapper and generated SVG, and can still be
overridden by site CSS of equal specificity or SVG inline tokens. (Added in OINK 1.3.1.)
Mathematics uses Hugo’s build-time KaTeX output and local CSS, without a browser math runtime. HTML and Print use the vendored stylesheet and preserve MathML and authored TeX. Validate mathematics with the current Hugo baseline defined in the architecture contract. Markmap uses the matching vendored KaTeX runtime. On narrow screens, numbered-equation captions wrap within the reading column; a long caption must not widen the page.
Swagger and Redoc accept an HTTP(S) specification URL or a path rooted under
static/; neither resolves page resources. Redoc treats leading and
non-leading slashes equivalently and joins local paths to baseURL. Only HTML
is interactive; Print, Markdown, and RSS render a static specification link.
Book
The book type extends the docs shell and follows the content tree or data/docs_nav.json.
book_number, book_part, book_kind, and book_status are presentation metadata. They do not change Hugo publication state.
Numbered kinds are fig, tbl, eq, and eg. Their default ID is <kind>-<num>.
eg requires a caption. eq without num is an unnumbered display formula.
xref names exactly one kind with optional page/anchor, or an anchor with explicit text.
Numbered examples have one framed body and caption.
Footnotes belong to the page document. Native numbered tables and fences keep them there.
Shortcode bodies are separate Goldmark documents, so footnote references in tbl, eg, fig, card, tab, field, or include warn and remain literal.
The check ignores text that has code syntax.
book-toc follows navigation order at depth 1–3. Each of the four book-* indexes collects one target kind.
Single-page Print keeps ordinary heading, footnote, and tab IDs exactly as regular HTML renders them.
Multi-page section and whole-Book Print rewrite cross-page links and namespace page-local headings, footnotes, and generated tab IDs to prevent collisions.
Local heading links match after one URL-fragment decoding step, so ordinary Markdown links to Unicode headings retain their target in an aggregate.
Explicit target IDs remain intact. Consumers opt into these potentially expensive aggregate outputs.
Consumer-authored raw HTML anchors and custom shortcode IDs remain global by default.
Add the boolean data-td-print-local attribute to an element with an id to declare a page-local anchor.
An aggregate namespaces that ID and its same-page links; ordinary HTML and single-page Print retain the original ID.
This lets legacy aliases and custom shortcode headings keep their existing deep links while remaining unique across aggregated chapters.
Unmarked authored IDs must remain unique within their aggregate.
Release and download
Release front matter uses one release_url matching https://github.com/<owner>/<repo>/releases/tag/<tag>.
The URL supplies owner, project, and tag. The page supplies the date. No remote release state is fetched.
Removed release, release_products, and release_group_by_product warn with their replacements. They provide no compatibility path.
The section index lists every page. It uses parsed project tag when available, otherwise the page title.
For layout: releases, HTML keeps this release index; section Print aggregates the full release bodies with chapter-local IDs, in both development and production builds.
Checksums accept canonical lines or one source resource, never both. Filenames cannot be paths. HTML adds local copying. Static outputs show complete hashes.
Downloads use data/download/<key>.yaml. Channels are rolling or pinned.
Only pinned URLs and commands interpolate ${version} and ${tag}.
Before publication, rolling channels remain usable, while pinned channels show pending.
Markdown renders the complete channel list. RSS omits the component.
Switching tabs remeasures collapsible code blocks in the newly visible panel.
Their expand and collapse controls remain usable after fonts settle and when
ResizeObserver is unavailable.
Verification
Shared output rules live in the architecture contract; exceptions are defined with their components above. Markdown and RSS set no browser runtime flags; Print retains only flags required by rendered print features. Source checks cover parameters, hook policy, runtime isolation, and migration; output checks compare HTML, print, Markdown, RSS, and LLMS goldens; browser tests cover interactive surfaces. Migration is documented in the migration contract.