Skip to content

Tabs

Copy editable source into an initialized application:

Terminal window
pnpm dlx @simurgh-ui/cli add tabs

For package consumption, import from the component subpath and load its optional recipe CSS:

import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from '@simurgh-ui/react/tabs';
import '@simurgh-ui/styles/tabs.css';
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from '@simurgh-ui/vue/tabs';
import '@simurgh-ui/styles/tabs.css';
import {
TabsComponent,
TabDirective,
TabPanelDirective,
} from '@simurgh-ui/angular/tabs';
import '@simurgh-ui/styles/tabs.css';

Omit the component stylesheet for fully headless styling. CLI-copied components use the local path written to simurgh.json and the copied application styles instead of these package imports.

The root coordinates a tab list, triggers, and panels. Home/End and arrow keys move focus and selection. Horizontal arrows reverse in RTL; vertical arrows do not.

No default visual treatment is applied. The public component supplies behavior and semantics; consumers provide layout and appearance.

Live component Tabs
Manage your account preferences.

The public component parts are listed in API surface below. Use only the parts needed by the example; compound components depend on their documented parent/child nesting.

Controlled state remains the application’s source of truth and must be updated from every change event. Uncontrolled state reads its default only when the component mounts. Do not pass both forms of the same state at once.

FrameworkExact state contract and reset behavior
ReactControlled: value + onValueChange. Uncontrolled: defaultValue. Reset controlled state by assigning the initial value; reset uncontrolled state by changing a React key.
VueControlled: v-model (modelValue + update:modelValue). Uncontrolled: defaultValue. Reset the model ref to its initial value; reset uncontrolled state with a changed Vue :key.
AngularControlled two-way binding: [(value)] (value + valueChange). Angular exposes no separate default input; initialize and reset the bound class field explicitly.

React

const initialValue = '';
const [value, setValue] = useState(initialValue);
const [resetKey, setResetKey] = useState(0);
<Tabs value={value} onValueChange={setValue}></Tabs>
<button onClick={() => setValue(initialValue)}>Reset controlled</button>
<Tabs key={resetKey} defaultValue={initialValue}></Tabs>
<button onClick={() => setResetKey((key) => key + 1)}>Reset uncontrolled</button>

Vue

<script setup lang="ts">
import { ref } from 'vue';
const initial = '';
const state = ref(initial);
const resetKey = ref(0);
</script>
<Tabs v-model="state"></Tabs>
<button @click="state = initial">Reset controlled</button>
<Tabs :key="resetKey" :defaultValue="initial"></Tabs>
<button @click="resetKey++">Reset uncontrolled</button>

Angular

initial = '';
value = this.initial;
reset() { this.value = this.initial; }
<simurgh-tabs [(value)]="value"></simurgh-tabs>
<button type="button" (click)="reset()">Reset</button>
<Tabs defaultValue="account" direction="rtl"><TabsList><TabsTrigger value="account">Account</TabsTrigger><TabsTrigger value="security">Security</TabsTrigger></TabsList><TabsContent value="account">Account settings</TabsContent><TabsContent value="security">Security settings</TabsContent></Tabs>

Real-world example: dynamic workspace tabs

Section titled “Real-world example: dynamic workspace tabs”

Use stable IDs as values, preserve the active value when data refreshes, and select a predictable neighbor if the active tab is removed.

const [activeTab, setActiveTab] = useState(workspaceTabs[0]?.id ?? '');
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
{workspaceTabs.map((tab) => (
<TabsTrigger key={tab.id} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
{workspaceTabs.map((tab) => (
<TabsContent key={tab.id} value={tab.id}>
{tab.content}
</TabsContent>
))}
</Tabs>;

Vue uses v-model="activeTab" with v-for; Angular uses [(value)]="activeTab" with *ngFor and trackBy. Never use array indexes as tab values when items can be reordered.

These keys describe behavior implemented by the adapters. Native Tab, Enter, and Space behavior still applies to descendant links, buttons, and form controls unless the component overrides it.

Concern / keysBehavior
Focus entry and exit (Tab / Shift+Tab)Tab enters at the active tab; Tab then moves into the active panel or next focusable element.
Navigation and activationArrow keys move and activate tabs; Home/End selects first/last.
EscapeNot handled by the component.
RTL differencesHorizontal previous/next arrows reverse in RTL.
TypeaheadNot supported.

Preserve the documented composition and accessible names when wrapping or restyling this component. See accessibility and RTL guidance for keyboard, focus, labeling, and directionality requirements.

  • React exports: Tabs, TabsList, TabsTrigger, TabsContent
  • Vue exports: Tabs, TabsList, TabsTrigger, TabsContent
  • Angular exports: TabsComponent, TabDirective, TabPanelDirective

Import these public symbols from the component subpath shown in Installation. Framework-specific props, events, slots, directives, methods, defaults, and native-attribute behavior belong in the API tables on this page; use the linked source only to verify the current implementation.

React API reference

An inherited-attributes entry means the component accepts the complete named React native interface, including its event handlers and ARIA and data attributes. Remaining attributes are forwarded to the rendered element unless the component behavior described on this page overrides them.

Complete props type: PropsWithChildren<{ value?: string; defaultValue?: string; onValueChange?: (value: string) => void; orientation?: Orientation; direction?: Direction; }>.

PropTypeDefault / requirement
childrenReactNodeundefined
defaultValuestringundefined
directionDirectionundefined
onValueChange(value: string) => voidundefined
orientationOrientationundefined
valuestringundefined

Inherited attributes: HTMLAttributes<HTMLDivElement>.

PropTypeDefault / requirement
childrenReactNodeundefined

Inherited attributes: ButtonHTMLAttributes<HTMLButtonElement>.

PropTypeDefault / requirement
childrenReactNodeundefined
valuestring | (readonly string[] & string)Required

Inherited attributes: HTMLAttributes<HTMLDivElement>.

PropTypeDefault / requirement
childrenReactNodeundefined
valuestringRequired
Vue API reference

Boolean props without explicit defaults use Vue’s false default. Undeclared attributes follow the fallthrough behavior stated for each component.

Vue attribute fallthrough is enabled for the rendered root.

PropTypeDefault / requirement
modelValuestring''
defaultValuestring''
orientationOrientation'horizontal'
directionDirection'ltr'
EventPayload
update:modelValuestring

Slots: default.

Exposed methods: none.

Vue attribute fallthrough is enabled for the rendered root.

No declared props.

No emitted events.

Slots: default.

Exposed methods: none.

Vue attribute fallthrough is enabled for the rendered root.

PropTypeDefault / requirement
valuestringRequired

No emitted events.

Slots: default.

Exposed methods: none.

Vue attribute fallthrough is enabled for the rendered root.

PropTypeDefault / requirement
valuestringRequired

No emitted events.

Slots: default.

Exposed methods: none.

Angular API reference

Inputs and outputs use their public template names. Public methods are callable through a template reference or ViewChild. Native attributes apply to the documented template root or directive host; they are not automatically forwarded through component hosts.

Component selector: simurgh-tabs. The template’s first native element is div.

InputTypeDefault / requirement
valuestring''
orientationOrientation'horizontal'
directionDirection'ltr'
OutputPayload
valueChangestring

Content projection: [tab], default.

Public method
select(value: string): void
navigate(event: KeyboardEvent): void

Attribute directive selector: [simurghTab]; behavior applies to its host element.

InputTypeDefault / requirement
simurghTabstring''

No outputs.

Content projection: none.

Public method
select(): void

Attribute directive selector: [simurghTabPanel]; behavior applies to its host element.

InputTypeDefault / requirement
simurghTabPanelstring''

No outputs.

Content projection: none.

No public methods.

Follow the framework example as a structural contract. Root components own shared state; parts that consume that state must stay under the root (or be attached to projected descendants in Angular). Parts described as conditional are optional until their corresponding interaction or semantic region is used. Do not render a state-consuming part by itself.

FrameworkRequired parent/child relationshipConditional partsSupporting types
ReactRender Tabs as the ancestor. Nest the listed parts inside that root so they can read its shared state/context.TabsList, TabsTrigger, TabsContent are conditional parts: include only those needed by the documented anatomy. A trigger/control and its matching content/item are required when that interaction is used.None.
VueRender Tabs as the ancestor. Nest the listed parts inside that root so they can read its shared state/context.TabsList, TabsTrigger, TabsContent are conditional parts: include only those needed by the documented anatomy. A trigger/control and its matching content/item are required when that interaction is used.None.
AngularCreate TabsComponent first. Attach listed directives to elements inside its projected content; nested component parts must remain inside it.TabDirective, TabPanelDirective are conditional parts: include only those needed by the documented anatomy. A trigger/control and its matching content/item are required when that interaction is used.None.

Accessible names, descriptions, and form labels remain required whenever the component’s purpose cannot otherwise be determined, even when the corresponding visual part is optional.

The adapters target the same user-visible behavior and accessibility contract. Their public shapes follow each framework’s conventions and are not expected to be symbol-for-symbol identical.

ConcernContract
Public compositionReact exports 4 parts, Vue 4, and Angular 3. These are intentional composition differences; use each framework’s example rather than translating symbol-for-symbol.
State and change eventsReact uses controlled/default props and callbacks for value; Vue uses v-model; Angular uses [(value)].
Children and contentReact uses children; Vue uses the slots listed above; Angular uses the documented content projection selectors.
Native attributesReact forwards the named native interface; Vue follows the stated fallthrough rule; Angular attributes apply to the component host unless an input, directive, or documented native root consumes them.
Imperative accessNo framework exposes an imperative handle for this component.

These differences are intentional adapter design. Behavioral or accessibility differences not stated on this page are parity defects rather than supported variations.

This component is not a form control and does not contribute a named value to FormData. Use it to structure or describe a form only when its purpose and accessibility guidance apply.

The table distinguishes component behavior from application-owned presentation. “Not supported” means there is no public state contract for that adapter; do not invent one with an undocumented attribute. Keep status and error messages accessible when they are rendered outside the component.

StateReactVueAngular
LoadingNot supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
EmptyNot supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
InvalidNot supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
Read-onlyNot supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
DisabledSupported with disabled={true} on the documented control or interactive part; its interaction is blocked. Disabled form controls are omitted from submission.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
ErrorNot supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.Not supported by this component API; handle this state in surrounding application UI.
Styling contract

The selectors below are used by the published component recipe or emitted consistently by an adapter. Treat these as the supported styling surface. Element order, anonymous wrappers, and undocumented descendants are implementation details and should not be targeted.

SurfaceStable hooks
Recipe classesNo component-specific recipe class; this stylesheet currently imports shared tokens only.
Stable DOM partsNo named data-slot parts are emitted; target the forwarded root class or attributes documented in the API.
Stable data attributesNo documented data-* hook.
ARIA/state selectors used by the recipeNo ARIA state selector is used by this recipe.
CSS custom properties consumedNo component-specific token consumption beyond the shared token import.

Prefer a semantic token override for theme-wide changes. Use the listed class or part selectors for a component-scoped override. When omitting recipe CSS, preserve state attributes and ARIA semantics even if your replacement styles use different selectors.

Load the optional component stylesheet for the default recipe, override semantic tokens for broad theme changes, or omit the recipe CSS for headless styling. See theming and styling hooks for import order, selectors, dark mode, RTL, and reduced-motion guidance.

Last verified on 2026-08-13 against Simurgh registry 0.1.1.