Primitives
33

Tabs

A tablist and its panels. SegmentedControl looks similar and is a different thing: that is a radiogroup on real inputs, so the browser gives it exclusive selection, arrow keys and a roving tabindex for free. A tab has no native element to stand on, so the keyboard is written — it lives in @ilomee/aura-core/listbox, next to the cursor a tablist shares with a listbox and a menu.

plain markup — no library on the page
rendered by aura-react
rendered by aura-vue
rendered by aura-svelte
built from aura-angular's own class helpers
What it is, and when to reach for it.
What it is, and when to reach for it.
What it is, and when to reach for it.
What it is, and when to reach for it.
What it is, and when to reach for it.

Code

index.html
<div class="aura-tabs">
  <div class="aura-tabs__list" role="tablist" aria-orientation="horizontal">
    <button id="t-tab-0" type="button" role="tab" class="aura-tabs__tab" aria-selected="true" aria-controls="t-panel-0" tabindex="0">Overview</button>
    <button id="t-tab-1" type="button" role="tab" class="aura-tabs__tab" aria-selected="false" aria-controls="t-panel-1" tabindex="-1">API</button>
    <button id="t-tab-2" type="button" role="tab" class="aura-tabs__tab" aria-selected="false" aria-controls="t-panel-2" tabindex="-1" disabled>Recipes</button>
  </div>
  <div id="t-panel-0" class="aura-tabs__panel" role="tabpanel" aria-labelledby="t-tab-0" tabindex="0">What it is, and when to reach for it.</div>
</div>
Demo.tsx
import { Tabs } from '@ilomee/aura-react';

const ITEMS = [
  { id: 'overview', label: 'Overview', content: 'What it is, and when to reach for it.' },
  { id: 'api', label: 'API', content: 'Every prop, with its default.' },
  { id: 'soon', label: 'Recipes', content: 'Not yet.', disabled: true },
];

/**
 * Arrow along the list. Selection follows focus, which is what WAI-ARIA recommends and what a
 * reader arrowing through cheap panels expects; `manual` is for when rendering a panel costs
 * something they did not ask to spend. The disabled tab is stepped over, not stopped at.
 */
export default function Demo() {
  return <Tabs items={ITEMS} />;
}
Demo.vue
<script setup lang="ts">
import { Tabs } from '@ilomee/aura-vue';

const ITEMS = [
  { id: 'overview', label: 'Overview', content: 'What it is.' },
  { id: 'api', label: 'API', content: 'Every prop.' },
];
</script>

<template>
  <Tabs :items="ITEMS" />
</template>
Demo.svelte
<script lang="ts">
  import { Tabs } from '@ilomee/aura-svelte';

  const ITEMS = [
    { id: 'overview', label: 'Overview', content: 'What it is.' },
    { id: 'api', label: 'API', content: 'Every prop.' },
  ];
</script>

<Tabs items={ITEMS} />

Used as aura-tabs.

demo.component.ts
<aura-tabs [items]="ITEMS" [(value)]="active" />

Props

Plain markup has no props — the classes below are the whole API. The four packages do nothing more than set these same classes for you.
NameTypeDefaultNotes
renderPanel(tab: AuraTab, index: number) => ReactNodeRender the active panel yourself — a form, a table, anything a string cannot be.
itemsAuraTab[] | null
valuetwo-waystring
defaultValuestring
onValueChangeevent(value: string) => void
orientation'horizontal' | 'vertical''horizontal'Which arrow keys the list owns. The other axis is left to the page — WAI-ARIA is explicit that Down inside a horizontal tablist is not ours to take.
manualbooleanfalseMove the cursor without selecting; Enter or Space selects. Off by default, because selection following focus is what WAI-ARIA recommends — turn it on when rendering a panel costs something the reader did not ask to spend.
NameTypeDefaultNotes
panelslot{ tab: AuraTab; index: number }
itemsAuraTab[] | null
modelValuetwo-waystring
update:modelValueevent(value: string) => void
orientation'horizontal' | 'vertical''horizontal'
manualbooleanfalse
NameTypeDefaultNotes
panelslotSnippet<[AuraTab, number]>
itemsAuraTab[] | null
valuetwo-waystring
orientation'horizontal' | 'vertical''horizontal'
manualbooleanfalse
NameTypeDefaultNotes
panelTemplateTemplateRef<{ $implicit: AuraTab; index: number }>
itemsAuraTab[] | null
valuetwo-waystring
valueChangeevent(value: string) => void
orientation'horizontal' | 'vertical''horizontal'
manualbooleanfalse
idstringPins the id prefix. Omitted, it is derived from the first tab rather than from an injected service — this component stays constructible with new so the pure-logic suites can reach it.

Emitted classes

Every framework emits the same classes — that is what makes the four libraries look identical. You can use them directly, without any component library.

ClassRoleNotes
.aura-tabsbaseRequired. A modifier does nothing without it.
.aura-tabs__listelement
.aura-tabs__tabelement
.aura-tabs__panelelement
.aura-tabs--verticalmodifier

Tokens and rules

components.css
marker
a border on the tab itself — one fewer node than a sliding indicator
rule
var(--bw-1) solid var(--border), with the tab overlapping it
selected
var(--accent) on the marker, var(--text) on the label