Tabs
A composable tabbed navigation component with polished defaults and full keyboard support.
Active tab state is handled by data-[state=active]: Tailwind utilities built into
the component — no manual styling required.
Overview
The MTabs component provides a fully accessible tab interface composed of four parts:
MTabs, MTabsList, MTabsTrigger, and MTabsContent.
All four parts ship with production-quality defaults — MTabsList renders with a muted
pill container, MTabsTrigger handles active state via built-in
data-[state=active]:bg-background data-[state=active]:shadow utilities, and
MTabsContent has a focus-visible ring. No Class props are required to get a
polished result.
Active tab state is managed internally via a CascadingValue context, so triggers and panels
stay in sync without manual wiring. Components expose data-state attributes
("active" / "inactive" on triggers) and data-disabled="true"
on disabled triggers — override visual states with Tailwind's data-[state=active]:* utilities
via the Class parameter.
Usage
Add the namespace import to use the component.
@using Marai.UI.Components.MTabsBasic Usage
Compose all four parts together and set DefaultValue to the Value of the tab
that should be active on first render. The components render styled out of the box:
Manage your account settings and preferences.
<MTabs DefaultValue="account" Class="w-full">
<MTabsList Class="inline-flex items-center rounded-md bg-muted p-1 text-muted-foreground">
<MTabsTrigger Value="account"
Class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all
data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Account
</MTabsTrigger>
<MTabsTrigger Value="password"
Class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all
data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Password
</MTabsTrigger>
<MTabsTrigger Value="notifications"
Class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all
data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Notifications
</MTabsTrigger>
</MTabsList>
<MTabsContent Value="account" Class="mt-2">
<p class="text-sm text-muted-foreground">Manage your account settings and preferences.</p>
</MTabsContent>
<MTabsContent Value="password" Class="mt-2">
<p class="text-sm text-muted-foreground">Change your password and security settings.</p>
</MTabsContent>
<MTabsContent Value="notifications" Class="mt-2">
<p class="text-sm text-muted-foreground">Configure how and when you receive notifications.</p>
</MTabsContent>
</MTabs>
Examples
Disabled Trigger
Set Disabled="true" on MTabsTrigger to prevent that tab from being activated.
The trigger receives disabled and data-disabled="true", letting you apply
disabled styles with Tailwind's disabled:* utilities.
This tab is active and selectable.
<MTabs DefaultValue="active" Class="w-full">
<MTabsList Class="inline-flex items-center rounded-md bg-muted p-1 text-muted-foreground">
<MTabsTrigger Value="active"
Class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all
data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Active
</MTabsTrigger>
<MTabsTrigger Value="disabled"
Disabled="true"
Class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all
data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
disabled:pointer-events-none disabled:opacity-50">
Disabled
</MTabsTrigger>
</MTabsList>
<MTabsContent Value="active" Class="mt-2">
<p class="text-sm text-muted-foreground">This tab is active and selectable.</p>
</MTabsContent>
</MTabs>
Full Customization
Use data-[state=active]:* utilities on MTabsTrigger to style the active state
without JavaScript or variant helpers. All visual design lives entirely in the Class strings
you provide.
Overview content with a bordered panel.
<MTabs DefaultValue="overview" Class="w-full">
<MTabsList Class="flex w-full border-b border-border">
<MTabsTrigger Value="overview"
Class="flex-1 py-2 text-sm font-medium text-muted-foreground transition-colors
data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Overview
</MTabsTrigger>
<MTabsTrigger Value="details"
Class="flex-1 py-2 text-sm font-medium text-muted-foreground transition-colors
data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Details
</MTabsTrigger>
<MTabsTrigger Value="settings"
Class="flex-1 py-2 text-sm font-medium text-muted-foreground transition-colors
data-[state=active]:text-foreground data-[state=active]:border-b-2 data-[state=active]:border-primary
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Settings
</MTabsTrigger>
</MTabsList>
<MTabsContent Value="overview" Class="mt-4 p-4 rounded-md border border-border">
<p class="text-sm text-muted-foreground">Overview content with a bordered panel.</p>
</MTabsContent>
<MTabsContent Value="details" Class="mt-4 p-4 rounded-md border border-border">
<p class="text-sm text-muted-foreground">Details content with a bordered panel.</p>
</MTabsContent>
<MTabsContent Value="settings" Class="mt-4 p-4 rounded-md border border-border">
<p class="text-sm text-muted-foreground">Settings content with a bordered panel.</p>
</MTabsContent>
</MTabs>
Customization
Custom Tailwind CSS Classes
The Class parameter on each sub-component is applied directly to its root element.
Use Tailwind utilities alongside data-[state=active]: variants for active state styling.
- Active trigger:
Class="data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm" - Full-width list:
Class="w-full"onMTabsList - Equal trigger width:
Class="flex-1"on eachMTabsTrigger - Panel spacing:
Class="mt-2 p-4"onMTabsContent - Disabled style:
disabled:opacity-50 disabled:pointer-events-noneonMTabsTrigger
Standard HTML Attributes
All sub-components pass arbitrary HTML attributes through to their root element via AdditionalAttributes.
You can add ARIA attributes, data attributes, and event handlers directly on any sub-component.
Accessibility
MTabsListrenders asrole="tablist"— addaria-labelto describe the tab group when needed- Each
MTabsTriggerrenders asrole="tab"witharia-selected,aria-controls, and a stableidset automatically - Each
MTabsContentrenders asrole="tabpanel"witharia-labelledbylinking back to its trigger andtabindex="0"so keyboard users can Tab into the panel content - Inactive triggers receive
tabindex="-1"(roving tabindex pattern); the active trigger receivestabindex="0" - Arrow key navigation activates the tab immediately (automatic activation mode)
- Disabled triggers receive the native
disabledattribute and are skipped during arrow key navigation - Sub-components accept arbitrary ARIA attributes via
AdditionalAttributes
| Key | Action |
|---|---|
| ArrowRight | Move to and activate next tab (wraps) |
| ArrowLeft | Move to and activate previous tab (wraps) |
| Home | Move to and activate first enabled tab |
| End | Move to and activate last enabled tab |
| Tab | Move focus from active tab trigger into the tab panel |
API Reference
MTabs
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Slot for MTabsList and MTabsContent elements. |
| DefaultValue | string | "" |
The Value of the tab that is active on first render. |
| Class | string? | null |
CSS classes applied directly to the root <div>. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the root element. |
MTabsList
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Slot for MTabsTrigger elements. |
| Class | string? | null |
CSS classes applied directly to the role="tablist" container. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the container (e.g. aria-label). |
MTabsTrigger
| Parameter | Type | Default | Description |
|---|---|---|---|
| Value | string | required | Unique identifier that matches a MTabsContent Value. Used to link the trigger and panel via ARIA. |
| ChildContent | RenderFragment? | null |
Tab label content. |
| Disabled | bool | false |
Disables the trigger. Sets native disabled and data-disabled="true"; keyboard navigation skips it. |
| Class | string? | null |
CSS classes applied directly to the tab <button>. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the <button> element. |
MTabsContent
| Parameter | Type | Default | Description |
|---|---|---|---|
| Value | string | required | Matches the MTabsTrigger Value that controls this panel. The panel renders only when its value is active. |
| ChildContent | RenderFragment? | null |
Content rendered when this tab panel is active. |
| Class | string? | null |
CSS classes applied directly to the role="tabpanel" element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the panel element. |
- Always set
DefaultValueto match one of theMTabsTrigger Valuestrings — leaving it empty means no tab is selected on load - Keep
Valuestrings lowercase and hyphen-separated to stay consistent with HTML conventions (e.g."account-settings") - Every
MTabsTriggermust have a matchingMTabsContentwith the sameValue - Add
aria-labeltoMTabsListwhen the surrounding context does not make the tab group's purpose clear