Public API
The public API is documented here; changes are listed in CHANGELOG.md. Anything not listed (internal includes, Sass partial paths, private --mt-p-* primitives, HTML details that no rule describes) can change in any release.
Stable names
| Area | Stable names |
|---|---|
| Settings | every key under modootoday: listed in Configuration |
| Design tokens | semantic and component custom properties --mt-* (not the primitives --mt-p-*); the generated tokens.json mirrors them |
| Markdown rules | the patterns in RULES.md and their output classes mt-*; the opt-out class mt-flat-list (no gap between list items below it) |
| Layouts | default, doc, post, archive, twin, llms, llms-full |
| Includes you may override | head-custom.html, footer-custom.html; modootoday/lang-switch.html and modootoday/brand/logo.html (takes size) can be included from your footer; override modootoday/brand/logo.html to draw your own logo |
| Includes you provide | _includes/sections/<name>.html, rendered at the end of a section or the hero by visual: <name> (see Customize) |
| Includes you may call | modootoday/archive.html with by and lang; modootoday/brand/icon.html with name (globe, sun, moon, search, menu, copy, check, external, prev, next, chevron-right, chevron-down, note, tip, important, warning, caution, edit, rss, tag, folder, brand), optional size (px, default 18), label (makes it role="img" with that name; otherwise aria-hidden) and class. Output: svg.mt-icon.mt-icon-<name> in currentColor; directional icons add .mt-icon-flip (mirrored in RTL) |
| Includes | modootoday/t.html (key=, optional n= / name=) prints a theme label for the page language |
| Data files | _data/i18n/<lang>.yml keys (optional overrides of the built-in catalogs), _data/nav/<name>.yml, _data/components.yml |
| Checker | the modootoday-check options, exit code 0 (no problems) and 1 (problems or bad usage) |
Markdown rules
md-components v1 is the name of the Markdown rules standard in RULES.md; it is not a CSS prefix. Output classes use mt-*.
Component structure
This is the markup you can target from your own CSS today. Classes and attributes in this table are part of the public API; elements inside them that the table does not name (wrappers, generated ids, the order of child elements) are not. The slot contract names the Markdown parts each component consumes.
| Component | Root | Parts you may style | State and variants |
|---|---|---|---|
| Page shell | body.mt-body |
a.mt-skip, header.mt-header > .mt-header-inner (.mt-site-title, nav.mt-nav, .mt-search, details.mt-lang.mt-menu (summary globe button, ul of a[lang][hreflang], current one aria-current), .mt-theme-toggle), main.mt-main, footer.mt-footer > .mt-footer-inner |
html[data-theme=light\|dark] (fixed scheme, or set by the toggle) |
| Doc page | article.mt-doc |
.mt-doc-layout > .mt-doc-main and .mt-doc-aside (holds nav.mt-toc), nav.mt-toc-mobile, nav.mt-breadcrumb, .mt-page-meta, nav.mt-pagination |
— |
| Doc sidebar | aside.mt-doc-nav > nav.mt-sidebar (from 64rem); details.mt-sidebar-mobile (below) |
.mt-sidebar-group, .mt-sidebar-title, the ul of links; the current link has aria-current=page |
.mt-doc-layout.mt-has-sidebar; [open] on the mobile menu |
| Page actions | details.mt-menu.mt-page-actions |
summary, the ul of button[data-mt-act=copy\|ai] (hidden until JS runs) and the View as Markdown a[type="text/markdown"] |
[open] |
| Hero | section.mt-hero |
the h1, lead p.mt-hero-lead (the first p right after the h1), link-only p (CTA buttons; first link is primary) |
.mt-hero-checks (front matter hero: {checks: true}: the first ul is a row of ✓ marks in --mt-ok) |
| Section | section.mt-section |
the h2 and its content |
[data-section] = name; [data-c] and .mt-c-<c> = component; .mt-cols-<n>; .mt-cta (on the section, or on a [data-mt-result] element inside it when one is present) |
| Section decor | .mt-has-decor on section.mt-hero or section.mt-section (front matter decor: <include>) |
.mt-section-decor[aria-hidden=true], the last child, holding _includes/sections/<include>.html; placed in the top end corner, no pointer events |
— |
| Scroll reveal | .mt-reveal on section.mt-hero or section.mt-section (front matter reveal: true) |
— | CSS only: fades in on scroll inside @supports (animation-timeline: view()) and prefers-reduced-motion: no-preference; shown as is otherwise |
| Full-width band | .mt-bleed on section.mt-hero or section.mt-section (front matter bleed: true), .mt-bleed-wide (bleed: wide) |
— | --mt-bleed-bg sets the band colour; inside a sidebar or table-of-contents layout the band stays in the main column; bleed sheet |
| Illustration | figure.mt-illustration[role=img][aria-label] (your own include) |
.mt-illustration-tag badge in the top start corner; its text belongs in the aria-label too, because the children of role=img are not read |
— |
| List styles | ul.mt-chips; li > strong.mt-item-title + span.mt-item-sep (with modootoday.lists) |
the li > a chips (44px tall, → after the text); the title and the : separator |
— |
Prose (no sections:) |
div.mt-prose |
the rendered body | — |
| Card grid | section.mt-section |
ul > li, its leading strong is the title |
.mt-cols-2, .mt-cols-3, .mt-cols-4, .mt-badge-bottom (c: features) |
| Tabs | section.mt-c-tabs[data-tabs] |
[role=tablist], [role=tab], [role=tabpanel] (built by JS; without JS the h3 + code blocks stay) |
[aria-selected] |
| Accordion | section.mt-c-accordion |
h3 > button[aria-expanded], the panel after it |
[aria-expanded=true\|false] |
| Steps / Timeline | section.mt-c-steps, section.mt-c-timeline |
ol > li or h3 items |
— |
| File tree | section.mt-c-filetree |
ul[aria-label], nested ul (items holding one are folders) |
— |
| Details / FAQ | section.mt-c-details, section.mt-c-faq |
details.mt-details, summary > h3, the content after it |
[open] |
| Parameter table | section.mt-c-params |
th[scope=col], .mt-param-required |
— |
| Link cards | section.mt-c-linkcards |
ul > li > a (the whole card is the link target) |
— |
| Quote | figure.mt-quote |
blockquote, figcaption |
— |
| Gallery | section.mt-c-gallery |
a.mt-gallery-thumb, a.mt-lightbox, .mt-lightbox-close |
:target on the open lightbox |
| Pricing | .mt-plans |
.mt-plan, its h3, link-only p.mt-actions |
.mt-plan-recommended with .mt-plan-flag |
| Subscribe | section.mt-c-subscribe |
form.mt-subscribe (when an action is set), label, input[type=email], button |
— |
| Split | section.mt-c-split (with .mt-split-start, .mt-split-5-7) |
.mt-section-in > .mt-section-body (the h2 and text) + .mt-section-visual (the visual: include). hero: {c: split} puts section.mt-hero in the same wrappers, with the h1, lead, check row and actions in .mt-section-body. visual: code puts the first code block in div.mt-section-visual.mt-split-code; chrome: terminal adds .mt-split-terminal and div.mt-split-bar (aria-hidden). |
— |
| Inline extras | div.mt-code, span.mt-progress, div.mt-embed |
.mt-code-title, progress, iframe |
— |
| Loading (optional sheet) | .mt-spinner, .mt-topbar, .mt-skeleton |
.mt-skeleton-text, -card, -image, -avatar, -row; .mt-ratio-16x9, -4x3, -1x1 |
[aria-busy=true], [role=status] |
| Banner | div.mt-banner |
its p and link; a.mt-back-to-top |
— |
| Alert | blockquote.mt-callout[role=note] |
p.mt-callout-title, the body |
.mt-callout-<type>, [data-variant] = note, tip, important, warning, caution |
| Badge | span.mt-badge |
the symbol (aria-hidden), the label |
.mt-badge-<status>, .mt-badge-tag, [data-variant] |
| Keyboard keys | kbd.mt-kbd |
kbd per key |
— |
| Figure | figure.mt-figure |
img, figcaption |
— |
| Post header | header.mt-post-header |
h1.mt-post-title, .mt-post-deck, .mt-byline (.mt-byline-authors > .mt-post-author, .mt-byline-dates > time), figure.mt-post-cover, nav.mt-series, p.mt-post-meta (tag badges) |
[data-part] = header, deck, byline, authors, dates, cover, terms |
| Post footer | article.mt-doc |
.mt-share button, nav.mt-post-nav, section.mt-related, .mt-news-correction, .mt-news-sources |
[data-part] = share, post-nav, related, sources |
| Post card | article.mt-card-post |
.mt-card-post-cover, .mt-card-post-meta, .mt-card-post-title, .mt-card-post-excerpt |
.mt-card-post-featured, [data-kind=news], .mt-post-list[data-variant=list\|grid\|magazine] |
| News front | div.mt-news-top |
.mt-news-lead, .mt-news-secondary, section.mt-news-latest, section.mt-news-section |
[data-category], .mt-badge[data-variant=breaking\|live] |
| Search | div.mt-search[role=search] |
the input, .mt-search-results |
— |
Slot contract
A slot is a named part of a section’s Markdown. Slot names are stable under the policy above, and declaring them never changes the generated markup. modootoday-check counts each slot in every section whose c: has a slots: entry in _data/components.yml, and fails when a count is outside the declared range.
| Slot | What it counts in the section (after its ## heading) |
Renders as |
|---|---|---|
lead |
Plain paragraphs before the first list, ###, code block or quote |
p |
items |
List items | ul > li or ol > li (cards, steps) |
panels |
### headings |
tab, accordion or step panels |
actions |
Link-only lines | CTA buttons (c: cta) |
code |
Fenced code blocks | pre (tab content) |
figures |
Lines that start with an image | figure.mt-figure |
Each slot takes required: true (same as min: 1), min and max (whole numbers, min not greater than max). For a component that has two valid shapes, slots: can be a list of alternative forms; a section passes when it satisfies any one of them. parts: lists the selectors from the component structure table that the component promises to keep; when the site has an api.md with that table, every part must appear in it. When Nokogiri is available (it is under github-pages), every part must also match inside a section of that component in the built HTML of the pages that use it; parts that component scripts add at runtime (role=, aria-*) are checked after the scripts run by the docs site QA.
features: {params: {cols: {allowed: [2, 3, 4]}, badge: {allowed: [bottom]}}, slots: {items: {min: 1}}, parts: [ul > li, .mt-cols-2, .mt-cols-3, .mt-cols-4, .mt-badge-bottom]}
tabs: {i18n: [tabs.label], slots: {panels: {min: 2}, code: {min: 2}}}
cta: {slots: {actions: {required: true}}, parts: [.mt-cta]}
accordion: {slots: {panels: {min: 1}}}
steps: {slots: [{items: {min: 1}}, {panels: {min: 1}}], parts: [section.mt-c-steps, ol > li]}
A component entry takes only params, i18n, slots and parts, and a slot rule only required, min and max. Any other key, an unknown slot name, a min above max or a list of fewer than two forms is an error, reported once per registry. Components without slots: are not counted, and a single-map slots: behaves exactly as before.
Right-to-left and math
- Pages in Arabic, Persian, Hebrew, Urdu and other RTL languages get
dir="rtl"fromlang; setdirection: rtlorltrin front matter (ordirection:in_data/i18n/<lang>.yml) to override. Components use logical properties and mirror; code stays left-to-right. - The theme bundles no math or diagram renderer; see Math and diagrams.
Posts and archives
Posts use the post layout: a date, author and tag line, the body, then a footer that links each category and tag to its archive section. Set the archive pages in _config.yml:
modootoday:
archive: {tags: /tags/, categories: /categories/}
Then add one page per taxonomy, for example tags.md:
---
title: Tags
layout: archive
archive: tags # or categories
---
The page shows its own body, an index of terms with post counts, and one section per term (id="tags-<slug>", newest post first). To place an archive anywhere else, call the include modootoday/archive.html from a layout or include, with by set to tags or categories and an optional lang. Archive styles live in modootoday-blog.css: the archive and post layouts load it, and any other page that calls the include needs blog_css: true in its front matter.
- A post’s language is its
lang(orsite.lang). An archive lists only posts in the page’s language;archive_lang: all(orlang="all"on the include) lists every post. Footer links of a post whoselangdiffers fromsite.langpoint at/<lang>/tags/, so add an archive page under each language directory. - Labels come from
archive.tags,archive.categories,archive.postsandarchive.emptyin_data/i18n/<lang>.yml, with English fallbacks. - Limits: classic GitHub Pages runs no custom plugin, so there is one page per taxonomy with an anchor per term, not one page per term. Posts get no Markdown twin; agents find them through the archive links. Jekyll puts the folder names above
_posts/intocategories, so keep posts in the top-level_posts/and setlang:in front matter.