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

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

jsx
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

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, panel aria-hidden / hidden, and panel tabindex when 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 use baseId: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).

See also