Tabs
A list of tabs that shows one panel at a time. Use it for sections of one page, views inside a panel, and the steps of a setup flow.
Tabs playground
Fictional project tabs. The third tab is disabled and has no panel.
Fictional project overview.
Usage
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@tulipfarm/sprout/tabs";
<Tabs defaultValue="members">
<TabsList aria-label="Members settings">
<TabsTrigger value="members">Members (12)</TabsTrigger>
<TabsTrigger value="invitations">Invitations</TabsTrigger>
</TabsList>
<TabsContent value="members">…</TabsContent>
<TabsContent value="invitations">…</TabsContent>
</Tabs>Tabs holds the active value. Pass defaultValue to let it manage itself, or value and onValueChange to control it from state or the address. Each TabsTrigger value matches one TabsContent. Give the list an aria-label that names the group.
Variants
default is a filled segmented control for a few settings groups.line underlines the active tab for page sections and views inside cards and popovers. steps fills the width for a numbered setup flow and uses the primary color, so the TulipFarm theme makes it green.
Settings groups
The default list fits short labels and counts. keepMounted keeps both panels in the page, so a list keeps its place and its pending changes.
Members settings
Default tabs with a count in the label. Both panels stay mounted.
- Priya Ramanpriya@meadow.exampleAdmin
- Alex Meadowalex@meadow.exampleMember
- Robin Fieldrobin@meadow.exampleMember
No pending invitations.
Page sections
<Tabs
value={tab}
onValueChange={(value) =>
navigate({ search: { tab: value === "access" ? undefined : value } })
}
>
<div className="max-w-full overflow-x-auto pb-1">
<TabsList variant="line" aria-label="Model settings">…</TabsList>
</div>
<TabsContent value="access" keepMounted>…</TabsContent>
</Tabs>Wrap a long line list in a horizontal scroller so it never widens the page. The underline and focus ring stay inside the list. Panels can show loading, empty, and error states with StatePanel, or render nothing until their data is ready.
Model settings
Line tabs read and write the address. Compaction stays empty until access loads.
/meadow/models- ClaudeResearch, SupportAvailable
- GPTAll teamsAvailable
- GeminiNo teams yetFallback: Claude
Summarize long conversations after 80% of the context window.
Applies to new tasks in every team. Earlier tasks keep their policy.
Agent page
Line tabs filtered by the person's capabilities. The list scrolls sideways on small screens.
Check setup, then start a task with this agent.
Weekly report writer is ready. Start a private task to try it.
The role, model, skills, and actions this agent may use.
Summarize last week's closed tasks. Preferred model: Claude.
Who can use or manage this agent, and whose team permissions it runs with.
Research team members can use this agent. Priya Raman manages it.
Turn execution on, limit the tables and Slack channels it may use, then activate the saved instructions.
Execution is off. Two tables and one Slack channel are allowed.
Attach this agent to a Slack channel so people can ask it there.
Not attached to a Slack channel.
Choose how this agent starts, including repeating schedules.
Runs every Monday at 09:00.
Technical details and deletion.
Agent ID agt_8f21c. Deleting revokes every activation.
Setup steps
<TabsList variant="steps" aria-label="Agent setup sections">
{steps.map((step, index) => (
<TabsTrigger key={step.id} value={step.id}>
<span aria-hidden="true" className="grid size-5 place-items-center">
{errors.has(step.id) ? <AlertCircle /> : done.has(step.id) ? <Check /> : index + 1}
</span>
{step.label}
{errors.has(step.id) && <span className="sr-only">, needs attention</span>}
</TabsTrigger>
))}
</TabsList>Put a number, a check, or a warning icon before each label, and add hidden text for the warning. Steps need room, so swap them for a select on small screens. The preview card uses a line list, and its Edit buttons move the setup to a step.
Agent setup
Steps tabs mark finished and invalid sections. Below 640px a select replaces them. Panels stay mounted, so drafts survive.
Your new agent
Personal agentDraft- SkillsNo skills selected
- ActionsNo actions selected
- Team placementPersonal agent
- SlackNot attached
- SchedulesManual use only
Try the draft with a private message. Nothing is sent.
Nothing runs until you review and activate.
Inside a popover
Put a line list in a bordered header inside a panel popover. Leave out a trigger and its panel when an option does not apply, and fall back to another value. Without keepMounted, a panel unmounts when it is hidden.
Table search
Line tabs inside a panel popover. Tables without text fields hide the Text tab.
API
The four parts wrap Base UI Tabs. Every Base UI prop works, including orientation, disabled on a trigger, activateOnFocus and loopFocus on the list, keepMounted on a panel, render, andclassName as a string or a state function.
Parts get data-slot values of tabs, tabs-list, tabs-trigger, and tabs-content. The list has data-variant, every part has data-orientation, and the active trigger has data-active. Mark trigger icons with data-icon="inline-start" or "inline-end" to tighten padding. tabsListVariants() returns list classes and tabsListVariantNames lists the variants.
Accessibility
The list is a tablist, each trigger is a tab witharia-selected, and each panel is a tabpanel labelled by its tab. Tab moves into the list and then to the panel. Arrow keys move between tabs and skip disabled ones, Home and End jump to the ends, and Enter or Space selects. Vertical lists use the up and down arrows.
Colors and the underline change over 120–180ms. Both stop when reduced motion is requested.
Theme tokens
Tabs use --sprout-secondary, --sprout-surface,--sprout-text, --sprout-muted, --sprout-line, --sprout-input, --sprout-primary, --sprout-focus, --sprout-radius-control, --sprout-shadow-control, and the motion tokens. They follow light, dark, and the TulipFarm theme.