M
Marai.UI
v1.0.0-alpha.7

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.

razor
@using Marai.UI.Components.MTabs

Basic 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:

Preview

Manage your account settings and preferences.

razor
<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.

Preview

This tab is active and selectable.

razor
<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.

Preview

Overview content with a bordered panel.

razor
<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.

Customization Examples
  • Active trigger: Class="data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
  • Full-width list: Class="w-full" on MTabsList
  • Equal trigger width: Class="flex-1" on each MTabsTrigger
  • Panel spacing: Class="mt-2 p-4" on MTabsContent
  • Disabled style: disabled:opacity-50 disabled:pointer-events-none on MTabsTrigger

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

  • MTabsList renders as role="tablist" — add aria-label to describe the tab group when needed
  • Each MTabsTrigger renders as role="tab" with aria-selected, aria-controls, and a stable id set automatically
  • Each MTabsContent renders as role="tabpanel" with aria-labelledby linking back to its trigger and tabindex="0" so keyboard users can Tab into the panel content
  • Inactive triggers receive tabindex="-1" (roving tabindex pattern); the active trigger receives tabindex="0"
  • Arrow key navigation activates the tab immediately (automatic activation mode)
  • Disabled triggers receive the native disabled attribute and are skipped during arrow key navigation
  • Sub-components accept arbitrary ARIA attributes via AdditionalAttributes
Keyboard Navigation
KeyAction
ArrowRightMove to and activate next tab (wraps)
ArrowLeftMove to and activate previous tab (wraps)
HomeMove to and activate first enabled tab
EndMove to and activate last enabled tab
TabMove 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.
Best Practices
  • Always set DefaultValue to match one of the MTabsTrigger Value strings — leaving it empty means no tab is selected on load
  • Keep Value strings lowercase and hyphen-separated to stay consistent with HTML conventions (e.g. "account-settings")
  • Every MTabsTrigger must have a matching MTabsContent with the same Value
  • Add aria-label to MTabsList when the surrounding context does not make the tab group's purpose clear