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.
| Name | Type | Default | Notes |
|---|---|---|---|
| renderPanel | (tab: AuraTab, index: number) => ReactNode | — | Render the active panel yourself — a form, a table, anything a string cannot be. |
| items | AuraTab[] | null | — | |
| valuetwo-way | string | — | |
| defaultValue | string | — | |
| 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. |
| manual | boolean | false | Move 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. |
| Name | Type | Default | Notes |
|---|---|---|---|
| panelslot | { tab: AuraTab; index: number } | — | |
| items | AuraTab[] | null | — | |
| modelValuetwo-way | string | — | |
| update:modelValueevent | (value: string) => void | — | |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | |
| manual | boolean | false |
| Name | Type | Default | Notes |
|---|---|---|---|
| panelslot | Snippet<[AuraTab, number]> | — | |
| items | AuraTab[] | null | — | |
| valuetwo-way | string | — | |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | |
| manual | boolean | false |
| Name | Type | Default | Notes |
|---|---|---|---|
| panelTemplate | TemplateRef<{ $implicit: AuraTab; index: number }> | — | |
| items | AuraTab[] | null | — | |
| valuetwo-way | string | — | |
| valueChangeevent | (value: string) => void | — | |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | |
| manual | boolean | false | |
| id | string | — | Pins 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.
| Class | Role | Notes |
|---|---|---|
| .aura-tabs | base | Required. A modifier does nothing without it. |
| .aura-tabs__list | element | |
| .aura-tabs__tab | element | |
| .aura-tabs__panel | element | |
| .aura-tabs--vertical | modifier |
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