VESYL UI

Tabs

Switch between sections of related content within a single view. Built on base-ui Tabs — keyboard navigation, roving focus, and ARIA wiring come for free.

Compose four parts: Tabs (root, owns the active value), TabsList (the trigger strip), TabsTrigger (one per section), and TabsContent (the panel for a value).

Track variants follow the same chrome language as buttons: default has a border and track fill, outline is stroke-only, ghost is bare (hover/active on items). line is the underline strip for page nav. Use gap on Tabs for space between the list and panel, or on TabsList for space between triggers. Both accept spacing-scale numbers or raw CSS values.

Default

Bordered recessed track — the usual segmented control. Active trigger lifts onto a raised neutral chip with a hairline border.

A summary of the account, its plan, and recent usage.

Outline

variant="outline" keeps the outer stroke but drops the track fill — useful on surfaces where a filled rail is too heavy.

A summary of the account, its plan, and recent usage.

Ghost

variant="ghost" has no rail (no border, no track fill). Inactive items get a soft hover wash; active is the same raised chip. Good for in-dialog side nav and light filter strips.

Every order, regardless of status.

Line (underline)

variant="line" drops the track and marks the active tab with a 2px underline. Right for page- or section-level navigation above content — e.g. Users / Permissions on a role detail page.

Members assigned to this role.

The underline is neutral (--foreground) by default. Add accent for a brand-colored underline — reserve it for when the tabs should draw attention: <TabsList variant="line" accent>.

With icons

Each TabsTrigger lays out its children in a row with a gap, and auto-sizes any leading svg to size-4. Drop a lucide icon in front of the label.

In-transit and delivered shipments.

Sizes

size controls trigger height and text size: md (default) or sm for dense toolbars. Set it once on TabsList — triggers inherit it.

size="sm" — compact, for dense toolbars.
size="md" — the default.

Vertical orientation

Set orientation="vertical" on the root to stack the list beside its content. Paired with variant="ghost", this is the in-dialog settings-nav pattern (Profile / Preferences). The demo uses gap={6} on the root rather than a call-site gap class.

Name, email, and avatar.

Controlled

Tabs is uncontrolled by default — give it defaultValue. To drive it yourself (e.g. to sync the active tab to the URL), pass value and onValueChange.

const [tab, setTab] = useState<"users" | "permissions">("users");

<Tabs value={tab} onValueChange={(v) => setTab(v as typeof tab)}>
  <TabsList variant="line">
    <TabsTrigger value="users">Users</TabsTrigger>
    <TabsTrigger value="permissions">Permissions</TabsTrigger>
  </TabsList>
  {/* … */}
</Tabs>;

Reference

Tabs

Root container. Extends base-ui Tabs.Root props.

PropTypeDefaultDescription
defaultValueanyInitially active tab value (uncontrolled)
valueanyActive tab value (controlled)
onValueChange(value) => voidFires when the active tab changes
orientation"horizontal" | "vertical""horizontal"Layout direction; also moves the active indicator
gapnumber | stringGap between list and panel (number = spacing scale; string = CSS)
classNamestringMerged onto the root

TabsList

The trigger strip. Extends base-ui Tabs.List props.

PropTypeDefaultDescription
variant"default" | "outline" | "ghost" | "line""default"Bordered track / stroke-only / bare / underline
size"md" | "sm""md"Trigger height (md = h-9, sm = h-8) and text size; inherited by triggers
gapnumber | stringFlex gap between triggers (number = spacing scale; string = CSS)
accentbooleanfalseline only — accent-colored underline instead of the neutral one
fullWidthbooleanfalseStretch the list and distribute triggers across its width

TabsTrigger

One tab button. Extends base-ui Tabs.Tab props.

PropTypeDefaultDescription
valueanyIdentifies which panel this opens

TabsContent

A panel. Extends base-ui Tabs.Panel props.

PropTypeDefaultDescription
valueanyMatches the trigger that reveals it