Skip to content

Tabs

In review
Live · web

Underline tabs: icons + a count badge; the underline slides, the panel swaps (Tab in, Arrow keys)

Overview
Status, model, and recent summary

Enclosed tabs: the sliding cyan underline, seated inside a recessed well (not a filled pill)

Chat
The live session transcript

A disabled tab: skipped by arrow keys, still legible

Editor
Edit the document source

Scrollable / overflow: many tabs scroll horizontally with edge fades

Overview
The "Overview" section. Scroll the row and the underline follows

Small size: same indicator, tighter rhythm

Overview
Status, model, and recent summary

Active section: overview · enclosed chat · workspace overview. Each tab swaps its panel below. The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform;
// this is the canonical API the published @dizyx/nockerl-react package exposes.)
import { NockerlTabs } from '@dizyx/nockerl-react';
export function SessionPanel() {
const [tab, setTab] = useState('overview');
return (
<NockerlTabs
label="Session sections"
value={tab}
onChange={setTab}
tabs={[
{ value: 'overview', label: 'Overview', icon: 'overview' },
{ value: 'activity', label: 'Activity', icon: 'activity' },
{ value: 'files', label: 'Files', icon: 'files', count: 12 },
{ value: 'settings', label: 'Settings', icon: 'settings', disabled: true },
]}
renderPanel={(v) => <SectionBody section={v} />}
/>
);
}
// Enclosed variant (the same cyan underline, inside a recessed well). Pass variant="enclosed".
<NockerlTabs label="Panel sections" variant="enclosed" value={tab} onChange={setTab} tabs={tabs} renderPanel={renderPanel} />
PropTypeDefaultDescription
tabs *TabItemDef[]The tabs, left-to-right. Each owns a panel rendered via renderPanel or its own panel.
value *stringThe active tab value (controlled). Exactly one tab is selected.
onChange(next: string) => voidFired with the next value. Ignored for a disabled tab.
label *stringAccessible name for the whole tablist (wired via aria-label).
variantNockerlTabsVariant'underline'Underline indicator (default) vs. 'enclosed': the same cyan underline seated inside a recessed well.
sizeNockerlTabsSize'md'Tab height + padding + type role.
renderPanel(value: string) => ReactNodeRenders the body for the active tab. Falls back to the tab's own panel.
onClose((value: string) => void) | undefinedCLOSABLE tabs (dev-tab-bar): when set, every tab grows a trailing close affordance (pointer) and DELETE / BACKSPACE closes the focused tab (the keyboard half of the ARIA deletable-tabs pattern; the X itself stays out of the tab order). The list stays CONTROLLED: this only reports the value; the host removes the tab + moves selection.

Also accepts the native <div> attributes (e.g. onClick, disabled, id, aria-*, data-*), forwarded straight through, plus a forwarded ref.

Cross-platform drift (tracked for reconciliation)

Tabs are the sectioned-content switcher: each tab owns a tabpanel (role="tablist"), distinct from a segmented control, which flips mutually-exclusive views/modes with no panels (role="radiogroup", one sliding pill on a connected track). Reach for tabs when each choice reveals its own body of content; reach for the segmented control for a compact view/mode toggle.

Real divergences between the shipped platforms:

  • A shipped component (all three platforms). Web, Android, AND Voice/Swift now expose the bespoke NockerlTabs-with-panels. Android uses Material 3 SecondaryTabRow + Tab + HorizontalPager (the all-cyan PrimaryTabRow was deliberately retired: muted inactive, cyan active + underline; see InboxSheet.kt). Voice/Swift ships the first-class NockerlTabs (v1.15.0). The native TabView was retired for Nockerl surfaces after it failed the contained-panel bar on a real Voice surface.
  • The slide (ratified, later amended): the indicator slides (transform + width, never the fill) on all three: web + Compose (its SecondaryIndicator animates via tabIndicatorOffset) + Swift (matchedGeometryEffect, teleporting under Reduce Motion). No raw system TabView remains on a Nockerl surface (Law 9 honored in spirit, since NockerlTabs is built to feel native).
  • Enclosed variant. This was a deliberate call and it holds: the enclosed variant is the same sliding cyan underline seated inside a recessed well, never a filled pill (a fill would ape the segmented control). Web, Compose, and Swift ship it (NockerlTabsVariant.enclosed seats the underline row inside a recessed NockerlWell).