react ~9 KB 0 deps v0.4.1 ↗ GitHub ↗

@mshafiqyajid/react-tabs

Headless tabs hook and styled component for React. Line, solid, and pill variants with a ResizeObserver-driven sliding indicator. Closeable tabs, drag-to-reorder, horizontal scrolling, lazy/force mount, typeahead navigation, controlled and uncontrolled, full keyboard navigation, ARIA-compliant.

Playground #

Overview content goes here.

Props
TSX
import { TabsStyled } from "@mshafiqyajid/react-tabs/styled";
import "@mshafiqyajid/react-tabs/styles.css";

<TabsStyled
  tabs={tabs}
  defaultValue="overview"
/>

Install #

npm install @mshafiqyajid/react-tabs

Quick start #

import { TabsStyled } from "@mshafiqyajid/react-tabs/styled";
import "@mshafiqyajid/react-tabs/styles.css";

const tabs = [
  { value: "overview", label: "Overview", content: <p>Overview</p> },
  { value: "features", label: "Features", content: <p>Features</p> },
];

<TabsStyled tabs={tabs} defaultValue="overview" />

Controlled #

const [tab, setTab] = useState("overview");
<TabsStyled tabs={tabs} value={tab} onChange={setTab} />

Variants #

<TabsStyled tabs={tabs} variant="line" />   {/* default */}
<TabsStyled tabs={tabs} variant="solid" />
<TabsStyled tabs={tabs} variant="pill" />

Disabled tab #

const tabs = [
  { value: "a", label: "Active",   content: <p>A</p> },
  { value: "b", label: "Disabled", content: <p>B</p>, disabled: true },
];

Closeable tabs #

Add closable: true (or the alias closeable) to a tab item to render a × button. Handle removals with onTabClose (or alias onClose).

const [tabs, setTabs] = useState([
  { value: "a", label: "Tab A", content: <p>A</p>, closable: true },
  { value: "b", label: "Tab B", content: <p>B</p>, closable: true },
]);

<TabsStyled
  tabs={tabs}
  onTabClose={(value) => setTabs((t) => t.filter((tab) => tab.value !== value))}
/>

Scrollable tab list #

Set scrollable to scroll the tab list horizontally instead of wrapping. Chevron buttons appear at the edges.

<TabsStyled tabs={manyTabs} scrollable />

Drag reorder #

Set sortable to enable HTML5 drag-to-reorder. The onReorder callback receives the new values array. Use reorderable instead if you prefer onReorder(fromIndex, toIndex).

const [tabs, setTabs] = useState(myTabs);

<TabsStyled
  tabs={tabs}
  sortable
  onReorder={(values) =>
    setTabs((prev) => values.map((v) => prev.find((t) => t.value === v)!))
  }
/>

Lazy / force mount #

// Only render panel content after first activation
<TabsStyled tabs={tabs} lazyMount />

// Always keep all panels in the DOM
<TabsStyled tabs={tabs} forceMount />

Headless #

import { useTabs } from "@mshafiqyajid/react-tabs";

const { activeValue, getTabProps, getPanelProps } = useTabs({
  tabs: [{ value: "a" }, { value: "b" }],
  defaultValue: "a",
});

<div role="tablist">
  <button {...getTabProps("a")}>Tab A</button>
  <button {...getTabProps("b")}>Tab B</button>
</div>
<div {...getPanelProps("a")}>Content A</div>
<div {...getPanelProps("b")}>Content B</div>

API #

TabItem shape: { value, label, content, disabled?, closable?, closeable? }

PropTypeDefaultDescription
tabsTabItem[]Tab definitions (see shape above)
variant"line" | "solid" | "pill""line"Visual style
size"sm" | "md" | "lg""md"Size variant
tone"neutral" | "primary" | "success" | "danger""neutral"Color tone
defaultValuestringfirst enabledInitially active tab (uncontrolled)
valuestringControlled active tab
onChange(value: string, reason: TabsChangeReason) => voidCalled when active tab changes. reason is "click" | "keyboard" | "programmatic"
activation / activationMode"automatic" | "manual""automatic"Arrow keys move focus AND activate (auto) or only move focus (manual)
orientation"horizontal" | "vertical""horizontal"Affects keyboard nav direction
scrollablebooleanfalseTab list scrolls horizontally with chevron buttons at edges
scrollActiveIntoViewbooleantrue when scrollableAuto-scroll active tab into view on activation
lazyMountbooleanfalseOnly mount panel content after first activation
forceMountbooleanfalseKeep all panels mounted regardless of activation
onTabClose / onClose(value: string) => voidFires when the × button on a closable tab is clicked (aliases)
sortablebooleanfalseEnable drag reorder; onReorder receives the new values array
reorderablebooleanfalseEnable drag reorder; onReorder receives (fromIndex, toIndex)
onReorder(values: string[]) => void or (from: number, to: number) => voidCalled after drag reorder (signature depends on sortable vs reorderable)
renderTab(ctx: TabsRenderTabContext) => ReactNodeCustom tab button content; button shell and a11y attrs stay owned by the component
renderPanel(ctx: TabsRenderPanelContext) => ReactNodeCustom panel rendering
classNamestringExtra class on the root element
styleCSSPropertiesInline style override

Typeahead is built in: pressing a letter key while the tab list is focused jumps to the next tab whose string label starts with the typed buffer (600 ms reset).

Edit this page on GitHub