Horizontal tab collection with index-matched content panels.
Install @wyrd-company/react or the Flutter package and use it directly —
your DESIGN.md does the styling.
catalog/contracts/tabs.ymlsource of truth · verbatim
$schema: https://thewyrdingway.dev/schemas/component-contract.schema.ymlcontractVersion: 0.1.0identity:name: Tabstag: wyrd-tabspackage: wyrd.component.tabstitle: Tabssummary: Horizontal tab collection with index-matched content panels.relationships:related-to: - quality-parity-and-gatesclassification:category: navigationcomposition: collectioninteractive: truetarget-strategy:react: requiredflutter: requiredsurface:properties: - name: valuetype:kind: stringrequired: falsecontrolled: trueserializable: truesummary: Controlled selected item value. - name: default-valuetype:kind: stringrequired: falseserializable: truesummary: Initial uncontrolled selected item value; when absent, the first non-disabled item is selected. - name: itemstype:kind: arrayitem:kind: objectfields:label:kind: stringvalue:kind: stringdisabled:kind: booleanrequired: falsedefault: []serializable: truesummary: Ordered tab items; disabled items render but are skipped by focus movement and activation.slots: - name: contentcontent: truerequired: truecardinality: one-or-manyaccepts: - component - contentsummary: Index-matched tab panels; the Nth slotted child is the panel for the Nth item. All panels stay mounted, and only the selected panel is visible and exposed to assistive technology.events: - name: value-changepayload:value:kind: stringbubbles: truecomposed: truecancelable: falsesummary: Emitted when the user activates an enabled tab with a different value.states: - name: defaultsource: platformsummary: Horizontal tab collection. - name: selectedsource: propertysummary: State derived from value, default-value, or the first enabled item. - name: focus-visiblesource: platformsummary: Keyboard-visible focus state on a tab. - name: disabledsource: propertysummary: Disabled tab item state.parts: - name: rootsummary: Tabs root.slots: - content - name: listsummary: Tablist container. - name: tabsummary: Repeated tab button. - name: panelsummary: Repeated tab panel; only the selected panel is visible and exposed to assistive technology.slots: - contenttokens:tab:background: color.surfaceforeground: color.on-surfaceselected-background: color.primaryselected-foreground: color.on-primaryhover-background: color.secondaryindicator-color: color.primaryindicator-thickness: 2pxpadding-x: spacing.mdpadding-y: spacing.smgap: spacing.xsmessages: []obligations:accessibility:role: tablistname: The surrounding content should provide context for the tab collection.keyboard: - Tab moves focus into the selected tab or focused tab. - ArrowLeft and ArrowRight move focus between enabled tabs with wrap-around without selecting. - Home and End move focus to the first and last enabled tabs. - Enter and Space activate the focused enabled tab.semantics: - Web projections expose role=tablist on the list, role=tab on each tab, aria-selected on tabs, and role=tabpanel on every index-matched panel. - Each panel is associated to its tab with aria-labelledby using stable per-instance ids. - Non-selected panels stay mounted but are hidden from visual rendering and assistive technology. - Disabled items are skipped by arrow-key focus movement and cannot be activated. - Flutter exposes selected button semantics for tabs; arrow-key traversal is not implemented in v1.behavior:model: collectioninteractions: - value ?? default-value ?? first enabled item determines the effective selected tab. - The Nth content child is the panel for the Nth item; items without a matching panel keep an empty mounted panel. - All panels stay mounted so panel-local state survives tab switches; only the selected panel is visible and exposed. - Arrow keys move focus only; click, Enter, and Space select.hostEffects: - Dispatch value-change when the user activates an enabled tab with a different value. - Generate stable per-instance ids for web tab and panel association.conditions: - rtl - reduced-motion - cvd-deuteranopia - density-compact - text-expansionsdui:status: serializableserializableProperties: - value - default-value - itemsserializableSlots: - contenteventBindings: - value-changenotes: Tabs serialize item records and index-matched panel content; the Nth serialized content child is the panel for the Nth item. Hosts must preserve order and keep all panels mounted while exposing only the selected panel visually and semantically.examples: - name: overviewstate: selectedproperties:default-value: overviewitems: - label: Overviewvalue: overview - label: Detailsvalue: details - label: Historyvalue: historyslots:content: - Project overview content. - Detailed settings content. - Change history content. - name: disabled-itemstate: disabledproperties:default-value: overviewitems: - label: Overviewvalue: overview - label: Detailsvalue: detailsdisabled: true - label: Historyvalue: historyslots:content: - Project overview content. - Detailed settings content. - Change history content.overrides:react:slots:content: childrenflutter:slots:content: child
01
Examples
Generated from the contract's examples block.
Previews are the published @wyrd-company/react component, themed by the
brand's role tokens — flip the site's light/dark toggle and they follow.
Initial uncontrolled selected item value; when absent, the first non-disabled item is selected.
items
array
Ordered tab items; disabled items render but are skipped by focus movement and activation.
03
Slots & events
Slot
Cardinality
Accepts
Summary
content
one-or-many
component, content
Index-matched tab panels; the Nth slotted child is the panel for the Nth item. All panels stay mounted, and only the selected panel is visible and exposed to assistive technology.
Event
Payload
Summary
value-change
value
Emitted when the user activates an enabled tab with a different value.
04
Tokens
The contract's token bindings. Open the shared contextual sidebar from any example;
primary swatches reflect the color you pick there. Error swatches show the active
preset's values; editing any knob switches to custom and resets error to TWW · Ink.
Binding
Token
tab.background
color.surface
tab.foreground
color.on-surface
tab.selected-background
color.primary
tab.selected-foreground
color.on-primary
tab.hover-background
color.secondary
tab.indicator-color
color.primary
tab.indicator-thickness
2px
tab.padding-x
spacing.md
tab.padding-y
spacing.sm
gap
spacing.xs
05
States
State
Source
Summary
default
platform
Horizontal tab collection.
selected
property
State derived from value, default-value, or the first enabled item.
focus-visible
platform
Keyboard-visible focus state on a tab.
disabled
property
Disabled tab item state.
06
Accessibility
From the contract's accessibility obligations. Role tablist; The surrounding content should provide context for the tab collection.
Tab moves focus into the selected tab or focused tab.
ArrowLeft and ArrowRight move focus between enabled tabs with wrap-around without selecting.
Home and End move focus to the first and last enabled tabs.
Enter and Space activate the focused enabled tab.
Web projections expose role=tablist on the list, role=tab on each tab, aria-selected on tabs, and role=tabpanel on every index-matched panel.
Each panel is associated to its tab with aria-labelledby using stable per-instance ids.
Non-selected panels stay mounted but are hidden from visual rendering and assistive technology.
Disabled items are skipped by arrow-key focus movement and cannot be activated.
Flutter exposes selected button semantics for tabs; arrow-key traversal is not implemented in v1.
Status: serializable. Tabs serialize item records and index-matched panel content; the Nth serialized content child is the panel for the Nth item. Hosts must preserve order and keep all panels mounted while exposing only the selected panel visually and semantically.