Skip to content

Contribute to the Playbook

The documentation on this site is created and edited by a variety of contributors. If you would like to contribute to the documentation, follow the steps below.

That is contributing to the documentation. For how work gets into the design system itself — who designs it, where the styleguide-versus-custom call is made, and who builds what — see How we work together.

  1. Request access to GitHub.

  2. Learn about using Markdown basic syntax.

  3. Create or edit any .md (or .mdx) files in src/content/docs/ directory. All files within are mapped to a filename based URL structure.

    Eg. the content at src/content/docs/start/contribute.mdx is available at /start/contribute as you can see in the current URL.

    Any documentation nested within directories will be automatically included in the sidebar navigation. For advanced use-cases you can configure sidebar in astro.config.js.

  4. Additional reading for more advanced content.

Every page’s frontmatter has two halves, and the split is the thing to remember: top-level keys belong to Starlight, everything under dxp: belongs to this repo.

---
title: Accordion # ── Starlight's, top-level
description: Collapsible content container…
sidebar:
order: 2
tableOfContents: false
dxp: # ── ours, all of it
status: ready
tile: ./accordion.svg
links:
figma: https://www.figma.com/design/…
subpage:
css:
team: platform
related:
- button
- '!icon-button'
---

If you are adding a key the Playbook invented, it goes under dxp:. If you are setting something documented in the Starlight frontmatter reference, it stays at the top level. That is the whole rule — before it existed, status: ready sat next to tableOfContents: false with nothing to tell an author which of the two this repo was responsible for.

The keys under dxp:, all optional:

Key What it does
status Lifecycle badge — ready, quarantine or deprecated. On a CDS component / layout / module / utility page, leaving it unset renders a red Unknown badge; treat that as “needs triage”, not as a default.
statusReviewed YYYY-MM-DD. Only while quarantined: the release-time review looked and decided “not yet”, which resets the two-month expiry clock.
statusNote One line on what a held page is waiting for. Maintainer-facing — the badge report prints it, the page never renders it.
tile Index-card illustration, e.g. ./accordion.svg. Inlined as raw SVG so it re-tints per brand and theme.
related A plain list of match patterns for the Related aside. Substrings are tested against every page’s id and title; prefix ! to exclude, suffix #anchor to deep-link.
links.figma The component’s entry point in the CDS Figma library. Promoted into the hero strip, and stripped from the public production build.
subpage.<tab> Per-tab metadata, authored on the root page rather than the tab file — css.team, react.team, react.storybook, design.figma, design.notes.
keywords Short taxonomy-style descriptors rendered as the subtitle under the page title, joined with “ – “.
internal true hides the page from the public production build entirely — no route, no sidebar entry — while keeping it on the internal previews and in dev.

Two things that look like exceptions but are not:

  • _meta.yml files are not namespaced. A folder’s tile, label, order, collapsed and tabs sit at the top level, because every key in that file is already ours or the auto-sidebar plugin’s — there is no boundary there to mark. A page’s dxp.tile and a folder’s tile are the same concept and share one schema definition.
  • There are no external links in related. It resolves page links only. A Figma link belongs in dxp.links.figma, and a Storybook link in dxp.subpage.react.storybook.

The schema is dxpSchema in src/content.config.ts — adding a key there is what makes it available to every page.

Components under cds/components/ use a tabbed layout — Design / CSS / React / Accessibility — driven by per-tab MDX files. The leaf page sits next to a <name>-pages/ folder; each MDX inside becomes a tab.

cds/components/
├── <name>.mdx # overview paragraph + <SubpageTabs />
└── <name>-pages/
├── _meta.yml # hidden: true, tabs: true
├── hero.astro # optional: the hero demo (not a tab)
├── design.mdx # sidebar.order 1
├── css.mdx # sidebar.order 2 — optional
├── react.mdx # sidebar.order 3 — optional
└── accessibility.mdx # sidebar.order 4

Only direct-child .mdx files become tabs, so the .astro files that sit alongside them — recipes, and hero.astro — never appear in the tab bar.

The two implementation tabs are both optional, and neither is required. A component documents CSS, React, both, or neither, and the file’s presence is the entire signal — there is no frontmatter flag. A component with neither is a legitimate state (designed, not built), and the implementation glyphs on the sidebar and the component index read exactly this (PLAT-3636).

What each tab carries:

  • Design — anatomy, when to use it, properties, states, modes, styling tokens, usage guidelines. Sourced from Figma.
  • CSS<DxpContent.Code> snippets for the canonical markup, variants, sizes, and states. Snippets include ARIA wiring so they are copy-pasteable. (This tab was called Develop until PLAT-3650; ?tab=develop links still resolve here.)
  • React — the React component’s props and examples, plus its Storybook link from dxp.subpage.react.storybook. Owned by the Dev team.
  • Accessibility## For designers and ## For developers sections covering canonical markup, keyboard interactions, and the relevant WAI-ARIA APG pattern.

Each tab file’s headings start at ##, which is what the panel’s “On this page” TOC lists.

Dropping a hero.astro into a <name>-pages/ folder gives that component a live, interactive preview above the tab bar. Nothing else is needed — no frontmatter, no import, no entry in _meta.yml; the file’s presence is the whole switch, and a component without one simply has no hero.

The file holds only the demo markup, hand-written, importing nothing:

<button class="tng-button is-primary">
<i class="tng-icon icon-download"></i>
<span>Download</span>
</button>

Everything around it — the stage framing, the CSS / React pills, Maintained by, the Figma and Storybook links — is derived and rendered by the hero component. Don’t author any of it here.

Two things to know when writing one:

  • Show the component working, not its parts. Copy the component’s recipe or its Design-tab <DxpContent.LiveAnatomy> markup as a starting point, then edit freely — de-annotate the pin labels, drop empty tng-slot placeholders (they read as a hole rather than a component), and give it real content. Unlike the Anatomy stage, this preview is interactive: buttons click, accordions open.
  • export const fill = true from the component script widens the demo out of the default narrow frame, for components that can’t lay out inside it — a table, a header bar.

Design tab MDX files use a small set of helpers. The card / grid primitives live in ~/components/layout/; the anatomy + token-table helpers live in ~/components/content/:

import * as DxpContent from '~/components/content/index.astro';
import * as DxpLayout from '~/components/layout/index.astro';

The alias names must be DxpContent and DxpLayout, not Content or Layout — Astro’s MDX integration appends Content[Symbol.for('mdx-component')] = true to every compiled MDX, and a user-side import * as Content shadows that and crashes the build (module-namespace objects are non-extensible).

Helpers for the Design tab template:

Component Purpose
<DxpLayout.CardGrid hasHero? min?> Responsive auto-fit grid. hasHero spans the first child; min controls the auto-fit floor (default 280px).
<DxpLayout.CardStack> Single-column stack of cards. Used for the Properties section.
<DxpLayout.TitleCard title tile? isHero? hasPlaceholder?> The everyday content card — <h3> title + body slot. tile="…" inlines an SVG from src/content/; frame slot accepts custom media. No frame renders unless one of those is given, or hasPlaceholder opts into a tng-slot wireframe. isHero is the lead-article variant: a larger title and an uneven, wide-guttered split.
<DxpLayout.Card> Pure layout primitive. Two children: a .tng-frame element + a single caption. Container-query horizontal/vertical at ≥ 600px. Use directly only when TitleCard’s shape doesn’t fit.
<DxpContent.Overview> + <DxpContent.OverviewItem number href?> A preview above a numbered two-column legend. On a component-group landing (buttons.mdx) the preview slot holds live markup of the whole family and each item links to its component; with no preview slot it falls back to a tng-slot wireframe, which is what it stands in as for an unwritten Anatomy section.
<DxpContent.Preview> A staged snapshot of live component markup, inert so a page may show many components without turning them into tab stops. Tune its inset with --preview-padding. Backs Overview’s preview and WhenToUseItem’s example.
<DxpContent.WhenToUse> + <DxpContent.WhenToUseItem name href?> The “Choosing …” table of a group landing: component / usage prose / live example, one row per component. Stacks below 50em. The example goes in the example slot, the prose in the default slot.
<DxpContent.TokenList name?, type?, numeric?> Vertical token swatch list. Canonical for the token-listing Fundamentals pages (fundamentals/{spacing,shape}.mdx and the Color / Typography Design tabs); don’t use it in a component’s design.mdx.

The legacy helpers — <DxpContent.Guidance>, <DxpContent.Card> + <DxpContent.PropertyGroup> + <DxpContent.Property>, <DxpContent.TokenValue>, <DxpContent.VariablesTable> + <DxpContent.VariablesRow> + <DxpContent.VariablesTextStyleRow> — were removed in the consolidation that introduced these primitives. The old Figma.Grid and Figma.ImageBlock went with them.

For the longer reference (per-tab content shape, SubpageTabs internals), see playbook/CONTRIBUTING.md and the repo-level AGENTS.md.