Tabs
A set of layered sections of content—known as tab panels—that are displayed one at a time.
Under the Hood
The RzTabs component family is powered by a lightweight Alpine.js controller that manages state, selection, and keyboard navigation in a fully CSP-compliant manner.
Demo
The main demo showcases a common use case for tabs: organizing user settings into "Account" and "Password" sections within a card.
<EditForm Model="_demoModel">
<DataAnnotationsValidator />
<RzInitialValidator />
<RzTabs DefaultValue="account">
<TabsList>
<TabsTrigger Value="account">Account</TabsTrigger>
<TabsTrigger Value="password">Password</TabsTrigger>
</TabsList>
<TabsContent Value="account">
<RzCard>
<CardHeader>
<CardTitle>Account</CardTitle>
<CardDescription>
Make changes to your account here. Click save when you're done.
</CardDescription>
</CardHeader>
<CardContent>
<RzFieldSet>
<RzFieldGroup>
<Field>
<FieldLabel For="@(() => _demoModel.Name)" />
<RzInputText For="@(() => _demoModel.Name)" />
<FieldError><RzValidationMessage For="@(() => _demoModel.Name)" /></FieldError>
</Field>
<Field>
<FieldLabel For="@(() => _demoModel.Username)" />
<RzInputText For="@(() => _demoModel.Username)" />
<FieldError><RzValidationMessage For="@(() => _demoModel.Username)" /></FieldError>
</Field>
</RzFieldGroup>
</RzFieldSet>
</CardContent>
<CardFooter>
<RzButton>Save changes</RzButton>
</CardFooter>
</RzCard>
</TabsContent>
<TabsContent Value="password">
<RzCard>
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Change your password here. After saving, you'll be logged out.
</CardDescription>
</CardHeader>
<CardContent>
<RzFieldSet>
<RzFieldGroup>
<Field>
<FieldLabel For="@(() => _demoModel.CurrentPassword)" />
<RzInputText For="@(() => _demoModel.CurrentPassword)" Role="
" />
<FieldError><RzValidationMessage For="@(() => _demoModel.CurrentPassword)" /></FieldError>
</Field>
<Field>
<FieldLabel For="@(() => _demoModel.NewPassword)" />
<RzInputText For="@(() => _demoModel.NewPassword)" Role="
" />
<FieldError><RzValidationMessage For="@(() => _demoModel.NewPassword)" /></FieldError>
</Field>
</RzFieldGroup>
</RzFieldSet>
</CardContent>
<CardFooter>
<RzButton>Save password</RzButton>
</CardFooter>
</RzCard>
</TabsContent>
</RzTabs>
</EditForm>Usage
Here is the basic structure for creating a set of tabs.
<RzTabs DefaultValue="account" class="w-[400px]">
<TabsList>
<TabsTrigger Value="account">Account</TabsTrigger>
<TabsTrigger Value="password">Password</TabsTrigger>
</TabsList>
<TabsContent Value="account">Make changes to your account here.</TabsContent>
<TabsContent Value="password">Change your password here.</TabsContent>
</RzTabs>Accessibility
RzTabs follows the WAI-ARIA Authoring Practices Guide Tabs pattern with automatic activation. A TabsList renders the tablist, each TabsTrigger renders a tab, and each TabsContent renders the associated tabpanel.
Tabs are best for switching between related panels without leaving the current page. Keep tab labels short, unique, and descriptive so assistive technology users can understand the available panels before activating them.
Semantics
TabsListrendersrole="tablist"with an accessible name fromAriaLabel.TabsListexposesaria-orientationashorizontalorvertical.- Each
TabsTriggerrendersrole="tab", stableid,aria-controls,aria-selected,tabindex, anddata-state. - Each
TabsContentrendersrole="tabpanel", stableid, andaria-labelledbypointing back to its tab. - Inactive panels are hidden with
hiddenandaria-hidden="true"; the active panel removes those hidden states. - Disabled tabs expose
aria-disabled="true"and are skipped by keyboard activation.
Keyboard interaction
| Key | When focus is on | Behavior |
|---|---|---|
Tab | TabsTrigger | Leaves the tablist and moves to the next focusable element in page order. Focus is not trapped. |
Shift + Tab | TabsTrigger | Moves to the previous focusable element in page order. |
ArrowRight | Horizontal TabsList | Moves focus to and automatically activates the next enabled tab, wrapping to the first enabled tab. |
ArrowLeft | Horizontal TabsList | Moves focus to and automatically activates the previous enabled tab, wrapping to the last enabled tab. |
ArrowDown | Vertical TabsList | Moves focus to and automatically activates the next enabled tab, wrapping to the first enabled tab. |
ArrowUp | Vertical TabsList | Moves focus to and automatically activates the previous enabled tab, wrapping to the last enabled tab. |
Home | TabsTrigger | Moves focus to and automatically activates the first enabled tab. |
End | TabsTrigger | Moves focus to and automatically activates the last enabled tab. |
Enter or Space | TabsTrigger | Activates the focused native button tab. The current API uses automatic activation, so these keys are not required after arrow navigation. |
Activation and focus
- RzTabs uses automatic activation: arrow navigation changes the active panel immediately and moves focus to the newly active tab.
- Only one enabled tab participates in sequential keyboard navigation with
tabindex="0"; inactive and disabled tabs usetabindex="-1". DefaultValueselects the initial tab when it matches an enabled trigger. If it is missing or disabled, the first enabled trigger is selected.- Focus is never trapped. Users can tab out of the tablist into active panel content or the rest of the page.
- Panels remain associated with tabs by stable IDs generated from the parent
RzTabsID and each tabValue.
Screen-reader behavior
- Screen readers can announce the tablist name, each tab label, selected state, disabled state, and the currently active panel relationship.
- RzTabs does not use live-region announcements for normal tab changes because
aria-selected, focus movement, and the labelled panel communicate the change without extra announcement noise. - Authors should provide meaningful panel headings or introductory text inside complex tab panels.
SSR and CSP notes
- The component renders SSR-safe roles, IDs, and ARIA relationships before Alpine initializes.
- Alpine updates selection, roving tabindex, hidden states, and
rz:tabs-changeevents on the client without Blazor interactivity. - The runtime is registered through the
core-commonbundle and works with the default and CSP runtime entrypoints.
Component Parameters
The following tables summarize the main parameters for each component in the Tabs family.
RzTabs
| Property | Type | Default | Description |
|---|---|---|---|
DefaultValue | string? | First tab | The value of the tab that should be active on initial render. |
ChildContent | RenderFragment | Required | The content, which should include a TabsList and TabsContent components. |
TabsList
| Property | Type | Default | Description |
|---|---|---|---|
ChildContent | RenderFragment | Required | The content, which should be a series of TabsTrigger components. |
AriaLabel | string? | "Tabs" | The accessible name for the tab list. |
Orientation | Orientation | Horizontal | The orientation of the tab list, affecting keyboard navigation. |
TabsTrigger
| Property | Type | Default | Description |
|---|---|---|---|
Value | string | Required | A unique value that associates the trigger with a TabsContent. |
Disabled | bool | false | If true, the trigger is disabled and cannot be selected. |
AsChild | bool | false | Merges behavior into a single child element instead of rendering a button. |
TabsContent
| Property | Type | Default | Description |
|---|---|---|---|
Value | string | Required | A unique value that associates the content with a TabsTrigger. |
Alpine API
This page uses the rzTabs Alpine x-data component. The runtime keeps tab selection, roving tabindex, panel visibility, and keyboard focus in sync after server rendering.
| Method | Parameters | Description |
|---|---|---|
refreshTriggers | — | Refreshes trigger registrations when tab markup changes. |
activateTrigger | trigger, focusTrigger | Selects an enabled trigger, optionally moves focus, and dispatches rz:tabs-change when the value changes. |
onListKeydown | KeyboardEvent | Handles orientation-aware arrow navigation, Home, and End for automatic activation. |