Tabs
Underlined tab bar. The single tab pattern in Lumen. Located at apps/web/components/ui/tabs.tsx.
Usage
const [tab, setTab] = useState<"threads" | "files" | "members">("threads");
<Tabs
value={tab}
onChange={setTab}
tabs={[
{ key: "threads", label: "Threads", count: threads.length },
{ key: "files", label: "Files", count: docs.length },
{ key: "members", label: "Members", count: members.length },
]}
/>
{tab === "threads" && <ThreadsList />}
{tab === "files" && <FilesList />}
{tab === "members" && <MembersList />}
Props
interface Tab<K extends string = string> {
key: K;
label: string;
count?: number; // optional count shown after label, muted
badge?: ReactNode; // optional badge (e.g. notification dot)
disabled?: boolean;
}
interface TabsProps<K extends string = string> {
tabs: Array<Tab<K>>;
value: K;
onChange: (key: K) => void;
className?: string;
}
Visual
- Underline on active tab (
border-b-2 border-accent, with-mb-pxso it merges with parent border) - Accent color text on active
- Muted text on inactive, hovers to primary
With count
{ key: "users", label: "Users", count: 42 }
Renders Users 42 with the count muted. Auto-formatted; no manual span needed.
Disabled tab
{ key: "billing", label: "Billing", disabled: true }
Grayed out, unclickable.
Common pattern: tabs + filter bar
<div className="flex items-center border-b border-line mb-4">
<Tabs value={tab} onChange={setTab} tabs={tabs} />
{/* Right-aligned filters, only visible on certain tabs */}
{tab === "users" && (
<div className="ml-auto flex items-center gap-2">
<SearchInput />
</div>
)}
</div>
The <Tabs> component has its own border-b. The parent border-b is redundant but kept for consistency — will be removed once every page uses Tabs exclusively.
Don't
- Don't hand-roll tab buttons. This is the canonical primitive. If you need a new variant (e.g., vertical tabs), extend this component.
- Don't switch
valuetype without updating all three tabs. TypeScript generics keeponChangetyped, buttypeof tabmust match. - Don't conditionally render the entire
<Tabs>— just hide/disable individual tabs viadisabled: true.