# Component contract

> The maintainer contract for OINK authoring primitives, validation, Book and release behavior, and output degradation.

---

LLMS index: [llms.txt](/llms.txt)

---

> [!NOTE] OINK 1.3.1 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](/docs/components/) section. This page defines the API and behavior
that those guides rely on.

## Authoring model {#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:

```yaml
markup:
  goldmark:
    renderer: { unsafe: true }
    parser:
      wrapStandAloneImageWithinParagraph: false
      attribute: { block: true }
```

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 {#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 {#validation}

Invalid author input follows the [architecture contract](/docs/design/architecture/):
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 {#component-behavior}

### Callouts and tabs {#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 {#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 {#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](/docs/design/architecture/#hugo-toolchain).
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 {#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-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 {#verification}

Shared output rules live in the [architecture contract](/docs/design/architecture/);
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](/docs/design/migration/).

---

Backlinks:

- [Upgrade](/docs/admin/upgrade/)
- [Design](/docs/design/)
- [Markdown-first authoring](/docs/design/decisions/authoring/)
- [Landing pages](/docs/design/landing/)
- [Migration boundary](/docs/design/migration/)
- [2026-09-19 community review](/docs/design/research/2026-09-19-upstream-review/)
- [Goldmark attributes](/docs/design/research/goldmark-attributes/)
