Tab Groups for Consistent Examples

A tab group is a named table of tab keys, each with a display title and an optional icon, defined once for a whole documentation set. It buys you two things on the published page. Every tabbed example that uses the group looks identical — the same label, the same icon, in the same order, on every page. And the reader’s choice follows them: pick Mac once, and every operating-system example in your documentation is already on Mac, on every page they open afterwards, for as long as their browser remembers.

That second payoff is the whole design constraint of the feature, and most of this page is about living with it well.

The key is what @tab matches, not the title

Each entry in a group is stored under a key. When the build renders a tab, it lower-cases the text you typed after @tab and looks that up as the key. The configured key is not normalized on the way in.

Two consequences fall out of that one line:

  • A key containing a capital letter can never match anything, because the left side of the comparison is always lower-cased.
  • The lookup is exact, not fuzzy. @tab C# will not find a key named csharp.

The failure mode matters more than the rule, because it is completely silent. An unknown key, or a group id that does not exist, produces no warning and no build failure:

What you wroteWhat rendersDoes it still sync?Is there a warning?
A key that is in the groupThe entry’s title, with its iconYes—
A key that is not in the groupThe raw text you typed, case preserved, no icon element at allYesNo
A group id that does not existRaw text for every tab, no iconsYesNo
No group id on the containerRaw text for every tab, no iconsNoNo

A typo does not break the page. It produces a tab that merely looks a bit unstyled next to its neighbors — which is why it survives review.

Membership is fixed, because syncing is by position

The rule that follows is unglamorous and non-negotiable: every container bound to a group lists every one of that group’s tabs, in the group’s key order. When a tab genuinely does not apply on a given page, write a panel that says so — “Windows is not supported for this command; use WSL and follow the Linux tab” — rather than dropping the tab. One extra panel is cheap. A reader silently redirected to the wrong instructions is not.

The exemption matters as much as the rule. A page-scoped ad-hoc id — a group id that no other container anywhere uses — syncs with nothing, because there is nothing else in its group. Such a container carries exactly the tabs its page needs, in whatever order suits it, and never pads its membership.

Bind every container to a group

Give every tab container a group id, including genuine one-offs. Not because a one-off will sync with anything, but because binding is what stops it inheriting.

A container with no group attribute that follows a grouped container on the same page picks up the previous container’s titles and icons while emitting no group of its own. It looks grouped, and it does not sync — the worst of both. An ad-hoc id costs one attribute and removes the problem entirely. The trade is that an ad-hoc id supplies no titles and no icons — only a configured group does that — so its @tab labels render exactly as typed.

The ===tabs-container syntax itself, the @tab lines, the blank-line rules and everything tabs do outside a documentation set are owned by Tabs. Only the attribute matters here:

===tabs-container {data-tabs-groupId="operating-system"}

Where the configuration lives

There is no single copy that serves both consumers, so a working set has two.

CopyEdited atRead byIf it is the only copy
Page typeSettings → Page Types → your set → DocumentationThe site buildThe group works on your published site and is missing from the editor’s Tab Group menu
WorkspaceSettings → General → Documentation → Tab GroupsThe editor’s Tab Group menuThe group appears in the toolbar and renders on the site with no titles and no icons
The Tab Groups list in Settings → General, with the reserved API Languages group and one custom group

Set both. The workspace copy propagates down to your documentation and API page types automatically, but fill-only: a new group is added, a changed title or icon never overwrites what the page type already has, and a removal never deletes anything. That is deliberate — it stops a workspace-level edit from silently rewriting a set someone has tuned — but it does mean the two copies drift once they both exist. Get a group right before it propagates, or edit the page-type copy directly afterwards.

One consequence for icons specifically: an icon class is only compiled into your site’s stylesheet if the class string appears somewhere in your site repository. A group that exists only in the CMS and has never been published can therefore render an icon element with no rule behind it. Publishing the page type fixes it, because that is what writes the group into the set’s data file. The full field-by-field reference for the object it lives in is at Documentation Configuration Reference.

The entry fields

Opening a group in the edit modal shows its derived id — generated from the group name and not editable — above the list of entries, each with its own title, icon and order.

FieldRequired?What it controlsRead byExample
titleyesThe label printed on the tab, replacing whatever the author typed after @tabSite build and CMS editorClaude Code
iconnoThe complete Font Awesome class pair rendered before the label — family and name, not a bare icon nameSite buildfa-brands fa-linux
iconClassnoExtra classes appended to iconSite buildtext-blue-600
iconStylenoEmitted as a raw inline style attribute on the icon. Keep it to custom propertiesSite build--fa-color: red
codeBlockLanguagenoSeeds each inserted tab with an empty code block in that language instead of an empty paragraphCMS editor onlybash
ordernoSorts the tabs the toolbar inserts, and the rows in the edit modalCMS editor only0

A group has no other fields. There is no per-group title, no per-group icon and no default tab — a group is nothing but a bag of tabs.

The reserved API Languages group

Every workspace has an api-languages group. It is created automatically from your API snippet languages — cURL, TypeScript, Python and Go by default — repaired automatically if entries are missing their icons, and deliberately filtered out of the editor’s Tab Group menu.

The editor’s Tab Group menu lists the groups you have defined. api-languages is deliberately absent from it: the group exists and renders, but it is maintained from your API snippet languages rather than inserted by hand.

It is product-generated rather than authored: the language tabs on a generated API reference page emit it directly. Do not rename it, trim its roster, restyle it (it uses a different icon weight from the others on purpose) or borrow it as a general code-language group for hand-written pages — the code regenerates and repairs it, so it will fight every edit. If you want language tabs on a hand-written page, give that page its own ad-hoc id. What the group actually drives is covered at API Reference Pages.

Designing a good set

The test for whether something deserves a predefined group is not “is this code” and not “would tabs look tidy here”. It is:

Is this a choice the reader makes once and keeps for the whole documentation set?

That is not a matter of taste. It falls straight out of the mechanism above: the choice is stored per group and replayed on every container in that group, forever. For a sticky identity — my operating system, my MCP client, my language — that is exactly right. For a per-task choice it is a bug generator: pick Video on the assets page and every later container in that group forces you to Video, including the ones you opened in order to read about images.

So keep groups small — two to four tabs, because fixed membership is the price of syncing — keep them closed, and put the recommended path first, since the first tab is the one that opens by default. Everything that fails the test gets a page-scoped ad-hoc id instead, or a table.

The groups these docs define

Group idTabs, in key orderThe sticky choice it representsWhere it is used
mcp-clientsClaude · Claude Code · ChatGPT · CursorWhich AI client the reader connects withEvery page that shows how to connect to Leed’s MCP servers
operating-systemLinux · Mac · WindowsThe reader’s operating systemInstalling the CLI, and the keyboard-shortcut tables
api-languagescURL · TypeScript · Python · GoThe reader’s HTTP client languageGenerated API reference pages — reserved and product-generated

Two groups, plus the reserved one. Every other tabbed comparison in these docs — including the three documentation layouts, which readers compare rather than commit to — uses a page-scoped ad-hoc id and syncs with nothing.

Here is the operating-system group as it is stored, and then as it renders:

"operating-system": {
  "linux":   { "title": "Linux",   "icon": "fa-brands fa-linux",   "order": 0 },
  "mac":     { "title": "Mac",     "icon": "fa-brands fa-apple",   "order": 1 },
  "windows": { "title": "Windows", "icon": "fa-brands fa-windows", "order": 2 }
}

You are reading the Linux panel, and this docs site has just remembered that. Open Installing the Leed CLI or Keyboard Shortcuts and each will already be showing you the Linux column.

On the published page the two containers stay in step: selecting Python in one switches the other to Python at the same moment, because both read the same group.

flowchart LR
    A["Reader clicks a tab<br/>in container 1"] --> B["Selection stored<br/>against the group"]
    B --> C["Container 1 switches"]
    B --> D["Container 2 switches<br/>to the same tab"]

Two things that look like bugs and are not

The editor’s Tab Group menu labels each group by title-casing its id, word by word. A group whose id is mcp-clients reads as Mcp Clients in that menu. It is cosmetic, it affects nothing that a reader ever sees, and there is no way to override it.

And an icon that looks correct in the CMS can render as a blank space on your site until the group has been published, for the compile reason described above. If an icon is missing on the published page but present in Settings, publish the page type before you go looking for a typo.

One note for anyone styling the strip

The selected-state rule has to key on the accessibility attribute — [aria-selected="true"] — and never on the active class the build stamps into the markup. That class lands on the first tab and never moves, because switching tabs updates ARIA state and panel visibility and leaves the class alone. A class-based rule underlines whichever tab the reader started on, permanently, no matter what they click. Everything else about theming the strip is at How Styling Works.

Icons come from the same bundled Font Awesome set your menus use, so the class pairs are interchangeable between the two — Building and Editing a Menu has the picker and the family names.

ESC