Tabs
Manual until a YAML contract lands in
monochrome/spec/components/.
Tablist with associated tabpanels. Root id: mcr:tabs:<rootId>. Each tab/panel pair shares <id> as mct:tabs:<id> / mcc:tabs:<id>. Selection state is aria-selected on tabs (plus tabindex, panel aria-hidden / hidden).
Composition
Root (mcr:tabs:…)
List (role="tablist")
Tab (role="tab"; direct children of List)
Panel (role="tabpanel"; one per Tab)Tab buttons must be direct children of the tablist. Core walks siblings with firstElementChild / nextElementSibling.
Usage
HTML
<div id="mcr:tabs:demo">
<div role="tablist" aria-orientation="horizontal">
<button
type="button"
role="tab"
id="mct:tabs:t1"
aria-selected="true"
aria-controls="mcc:tabs:t1"
tabindex="0"
>
Overview
</button>
<button
type="button"
role="tab"
id="mct:tabs:t2"
aria-selected="false"
aria-controls="mcc:tabs:t2"
tabindex="-1"
>
Features
</button>
</div>
<div
role="tabpanel"
id="mcc:tabs:t1"
aria-labelledby="mct:tabs:t1"
aria-hidden="false"
tabindex="0"
>
Overview content...
</div>
<div
role="tabpanel"
id="mcc:tabs:t2"
aria-labelledby="mct:tabs:t2"
aria-hidden="true"
hidden
tabindex="-1"
>
Features content...
</div>
</div>React
import { Tabs } from "@monochrome-ui/react"
<Tabs.Root defaultValue="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="features">Features</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview">Overview content...</Tabs.Panel>
<Tabs.Panel value="features">Features content...</Tabs.Panel>
</Tabs.Root>Vue
<script setup lang="ts">
import { Tabs } from "@monochrome-ui/vue"
</script>
<template>
<Tabs.Root default-value="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="features">Features</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview">Overview content...</Tabs.Panel>
<Tabs.Panel value="features">Features content...</Tabs.Panel>
</Tabs.Root>
</template>Parts
Root
Required. Element: div.
| Kind | Name | Type / value | Default | Description |
|---|---|---|---|---|
| attr | id |
mcr:tabs:<rootId> |
Root boundary. | |
| prop | defaultValue |
string |
(required in wrappers) | Initial selected tab value. Seeds aria-selected / panel visibility on mount. After interaction, DOM ARIA is truth. |
| prop | orientation |
"horizontal" | "vertical" |
"horizontal" |
Written to List as aria-orientation. |
There is no controlled value prop on Root. Selection after mount is whatever the DOM says.
List
Required. Element: div.
| Kind | Name | Type / value | Description |
|---|---|---|---|
| attr | role |
"tablist" |
|
| attr | aria-orientation |
"horizontal" | "vertical" |
Drives which arrow keys move focus. |
Children: Tab buttons only as element children (decorative nodes are skipped if non-element, but prefer tabs only).
Tab
Required (one or more). Element: button.
| Kind | Name | Type / value | Default | Description |
|---|---|---|---|---|
| attr | type |
"button" |
||
| attr | role |
"tab" |
||
| attr | id |
mct:tabs:<id> |
Wrappers build <id> from root base + value. |
|
| attr | aria-selected |
"true" | "false" |
Selected tab is "true". |
|
| attr | aria-controls |
mcc:tabs:<id> |
||
| attr | tabindex |
0 selected / -1 otherwise |
Roving tabindex. | |
| attr | aria-disabled |
"true" | absent |
Skipped when "true". |
|
| prop | value |
string |
(required) | Joins with root id to form tab/panel ids; matches Panel value. |
| prop | selected |
boolean |
derived from defaultValue |
Override initial selection for this tab. |
| prop | disabled |
boolean |
false |
Emits aria-disabled. |
Panel
Required (one per Tab). Element: div.
| Kind | Name | Type / value | Default | Description |
|---|---|---|---|---|
| attr | role |
"tabpanel" |
||
| attr | id |
mcc:tabs:<id> |
||
| attr | aria-labelledby |
mct:tabs:<id> |
||
| attr | aria-hidden |
"true" | "false" |
Inverse of selected. | |
| attr | hidden |
present | absent | present when not selected | |
| attr | tabindex |
0 selected / -1 otherwise | absent |
Present when panel is focusable. | |
| prop | value |
string |
(required) | Must match Tab value. |
| prop | selected |
boolean |
derived from defaultValue |
Override initial visibility. |
| prop | focusable |
boolean |
true |
When false, omit tabindex on the panel. |
Notes
- Activation: Clicking an unselected, non-disabled tab walks all siblings once: previously selected off, clicked tab on. Updates
aria-selected,tabindex, panelaria-hidden/hidden, and paneltabindexwhen the attribute exists. - Already selected: Clicking the selected tab is a no-op.
- Ids: Root
mcr:tabs:<rootId>. Pair tabs/panels with the same<id>suffix. Wrappers usebaseId:value. - Orientation: Horizontal uses ArrowLeft/ArrowRight; vertical uses ArrowUp/ArrowDown. Home/End always apply.
- Structure independence of panels: Panels need not be immediate siblings of the tablist; linkage is via
aria-controls/ ids. Tab buttons must still be direct children of the tablist.
Keyboard
| Key | Orientation | Behavior |
|---|---|---|
| Enter / Space | any | Activate focused tab (synthesized click) |
| ArrowRight / ArrowLeft | horizontal | Next / previous tab |
| ArrowDown / ArrowUp | vertical | Next / previous tab |
| Home | any | First tab |
| End | any | Last tab |
Disabled tabs are skipped. Focus moves with selection (automatic activation).