ink-ui

πŸ—‚οΈ Tabs

Inline tab strip for grouped interactive screens.

<Tabs> renders an inline tab strip. Active tabs are bold and highlighted; inactive tabs are dimmed. Optional counts render as a soft chip beside the label. Tabs can be grouped β€” a divider is drawn where the group value changes between neighbouring tabs.

import { Tabs } from "@kud/ink-ui"
import type { TabItem } from "@kud/ink-ui"

πŸ”§ Props

PropTypeDescription
activeTActive tab value
itemsTabItem<T>[]Tabs to render
widthnumberAvailable width; defaults to the terminal's columns, and never folds when that is unknown. When the full strip does not fit, inactive icon tabs fold to icon plus the count chip

TabItem<T> shape

FieldTypeDescription
valueTStable tab identifier
labelstringVisible tab label
iconstringOptional single glyph drawn between the marker gutter and the label; takes the label's style, and the active underline spans icon, space and label, stopping before the count chip
countnumber | nullOptional count drawn as a soft chip beside the label; null (not loaded yet) draws a chip holding – at a two-digit width
warningnumberOptional count of things in the tab that warn, drawn inside the count chip, after the count, as the warning mark and the number in the warning colour (QA (27 ⚠ 1)); 0 or absent draws nothing, and it survives folding
groupstringOptional group name; a divider is drawn between tabs whose group differs from the previous tab

πŸ’‘ Examples

<Tabs
  active="open"
  items={[
    { value: "open", label: "Open", count: 3 },
    { value: "done", label: "Done", count: 7 },
  ]}
/>

Grouped tabs

<Tabs
  active="qa"
  items={[
    { value: "qa", label: "QA", count: 22, group: "board" },
    { value: "off", label: "Off board", count: 4, group: "board" },
    { value: "prs", label: "My PRs", count: 3, group: "github" },
  ]}
/>

A divider (β”‚) appears between "Off board" and "My PRs" because the group changes from board to github. Tabs without a group, or with the same group as their neighbour, render without a divider. Navigation (Tab, ←, β†’) is unchanged β€” dividers are never a stop.

Icons and narrow folding

<Tabs
  active="open"
  width={40}
  items={[
    { value: "open", label: "Open", count: 3, icon: "β—†" },
    { value: "done", label: "Done", count: 7, icon: "β–²" },
  ]}
/>

An icon is part of the label, not a second marker: it renders one space before the label in the label's own style, and the active underline spans icon, space and label together. The marker gutter is untouched, so a pulse appearing beside an icon moves nothing.