Atom · beta · v1.0.0 · owner: Site system lead · contract 19 of 20 points (open: figma)
Do and don't
Do: A preset chip
Rendered by control.tsOpen example: Control · Do: A preset chip Use the full-page example to follow page links.
Do: A pressed toggle
Rendered by control.tsOpen example: Control · Do: A pressed toggle Use the full-page example to follow page links.
Don't: A numbered option
Rendered by control.tsOpen example: Control · Don't: A numbered option Use the full-page example to follow page links.
Don't: A control as navigation
Rendered by control.tsOpen example: Control · Don't: A control as navigation Use the full-page example to follow page links.
Generated API variants, rendered live
Generated variants of the first authored example, from selected boolean and enum props. These are API explorations, not authored state declarations or verification results. Hover and focus respond to the pointer and the keyboard in the examples above.
Base authored example
Rendered by control.tsOpen example: Control · Do: Base authored example Use the full-page example to follow page links.
pressed
Rendered by control.tsOpen example: Control · Do: pressed Use the full-page example to follow page links.
Rules
| Rule | Why | Check |
|---|---|---|
| Toggles and segments expose aria-pressed. | Without it, a screen reader cannot tell which option is on. | Automated: axe on showcase pages (tests/site/pages.spec.ts) |
| Controls only appear inside a ShowcaseFrame or a form. | Elsewhere they would look like actions without a purpose. | Automated: scripts/site/system/check-pages.ts |
API
| Prop | Type | Required | Default | What it does |
|---|---|---|---|---|
kind | chip | toggle | segment | Yes | Which control. | |
label | string | Yes | The option, in words. | |
pressed | boolean | No | false | Whether it is on (aria-pressed). |
attrs | html | No | Data attributes the showcase script reads. |
Contract details
The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests
Identity
| Aspect | Value |
|---|---|
| Id | control |
| Layer | Atoms |
| Category | action |
| Owner | Site system lead |
| Status | beta |
| Version | 1.0.0 |
| Source | apps/showcase/site/system/atoms/control.ts (private repository; the contract and the render function are this one module) |
| Version | Date | Change |
|---|---|---|
| 1.0.0 | 2026-10-26 | The small controls inside showcases: chips, toggles and segmented choices. |
Use when
- A showcase lets the reader change what it shows. Why: Trying it is the proof; the control must look like the site's other controls.
- Choosing one of a few presets. Why: Chips show every option at once.
Do not use when
- For the page's main action. Why: That is the primary button.
- For navigation. Why: Links take the reader somewhere; controls change the page in place.
Anatomy
| Part | What it is |
|---|---|
| root | button.chip, button.toggle or a .seg in a .segmented group. |
| label | The option's words. |
Made of: tokens:type, tokens:colour, tokens:radius.
Used in: playback-controls, academy-lesson, agent-check, autonomy-demo, before-after, layers-demo, ops-board, read-your-stylesheet, roles-picker, score-us.
Variants
| Variant | When | Why |
|---|---|---|
| chip | A preset among a few. | All choices visible, one tap each. |
| toggle | On or off. | aria-pressed tells assistive technology the state. |
| segment | One of two to four modes. | A segmented group shows that the choices exclude each other. |
States
| State | Rendered or not applicable |
|---|---|
| default | Outlined in --line-strong |
| hover | Background --surface-2. |
| focus-visible | The --focus ring. |
| active | Pressed: ink background, cotton text (aria-pressed=true). |
| disabled | A caller may pass the native disabled attribute through attrs. The atom does not own playback, hide itself or manage an async action. |
| loading | Not applicable: No intrinsic pending/async API; the containing feature owns any async action and its status. |
| empty | Not applicable: A visible label is required content; the renderer has no empty-state variant or nonempty-label validation. |
| error | Not applicable: The atom performs no validation/fetch; the containing feature owns action and data failures. |
| current | The selected segment uses the pressed style. |
| visited | Not applicable: Not a link. |
| expanded | Not applicable: Does not disclose content. |
Tokens
| Part | Token |
|---|---|
| root.border | --rule-hair --line-strong |
| root.font | --type-small or --type-label |
| pressed.background | --ink |
| pressed.color | --cotton |
| root.min-height | 44 px |
| --control-border-line-strong | var(--line-strong) |
| --control-border-radius-radius | var(--radius) |
| --control-padding-space-3 | var(--space-3) |
| --control-padding-space-6 | var(--space-6) |
| --control-font-type-label | var(--type-label) |
| --control-color-ink | var(--ink) |
| --control-border-left-line-strong | var(--line-strong) |
| --control-background-ink | var(--ink) |
| --control-color-cotton | var(--cotton) |
| --control-padding-space-1 | var(--space-1) |
| --control-padding-space-4 | var(--space-4) |
| --control-background-teal-soft | var(--teal-soft) |
| --control-color-teal-ink | var(--teal-ink) |
| --control-background-saffron-soft | var(--saffron-soft) |
| --control-color-saffron-ink | var(--saffron-ink) |
| --control-background-indigo-soft | var(--indigo-soft) |
| --control-background-madder-soft | var(--madder-soft) |
| --control-color-madder-ink | var(--madder-ink) |
| --control-gap-space-3 | var(--space-3) |
| --control-color-ink-2 | var(--ink-2) |
| --control-margin-right-space-2 | var(--space-2) |
| --control-gap-space-5 | var(--space-5) |
| --control-padding-space-5 | var(--space-5) |
| --control-border-ink | var(--ink) |
| --control-background-surface | var(--surface) |
| --control-background-line | var(--line) |
| --control-transition-dur-slow | var(--dur-slow) |
| --control-top-space-1 | var(--space-1) |
| --control-left-space-1 | var(--space-1) |
| --control-transition-ease-standard | var(--ease-standard) |
| --control-background-teal-2 | var(--teal-2) |
| --control-background-cotton | var(--cotton) |
Layout
| Aspect | Value |
|---|---|
| breakpoints | Required review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; groups wrap onto more lines on phones. |
| wrapping | Labels stay on one line; the group wraps. |
| textExpansion | Wraps the group, not the label. |
Spacing and rhythm
| Aspect | Value |
|---|---|
| internal | --space-3 by --space-5. |
| external | Gap --space-3 inside a group. |
| rhythm | Inside a showcase's control row. |
Motion
| Aspect | Value |
|---|---|
| moves | Background colour. |
| trigger | Hover and press. |
| duration | --dur-fast |
| easing | --ease-standard |
| interruptible | Yes. |
| reduced | Kept: a colour change is not motion. |
| why | Instant feedback that the control took the press. |
Runtime and reuse
Render-only element; mount any registered child enhancements separately. Preserve required Site CSS, fonts, responsive and locale rules from nativeElement.
Accessibility passport
| Aspect | Value |
|---|---|
| role | button (with aria-pressed for toggles and segments) |
| name | The label |
| keyboard | Tab to it, Enter or Space to press. |
| focus | In order within its showcase. |
| screenReader | Reads the label and 'pressed' or 'not pressed'. |
| contrast | Ink on cotton 15.9:1; pressed cotton on ink 15.9:1. |
| target | 44 px tall. |
Content
| Slot | Length | Voice |
|---|---|---|
| label | 3 words | The option's name |
Good: “Settings page” · “With an operating model”
Bad: “Option 1”
Japanese: Showcase strings come from the interactives' i18n tables (scripts/site/interactives/i18n.ts).
Icons and illustrations
None: this element uses no icon or illustration of its own.
Decisions and exceptions
Usage
35 uses across the built pages (measured by npm run site:system).
Tests
| Aspect | Value |
|---|---|
| unit | tests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns |
| visual | Not yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts |
| a11y | tests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark |
| parity | scripts/site/system/check.ts: Library examples are rendered by the same render function the site imports |
Figma
not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.