npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zoblocks/tabs

v0.2.0

Published

Tabs that know what they are. Four semantic modes — view switch, navigation, form value, wizard — across eleven skins, with the WAI-ARIA keyboard model, priority-plus overflow and WCAG 2.2 AA states.

Readme

@zoblocks/tabs

Tabs that know what they are. Four semantic modes across eleven skins, with the WAI-ARIA keyboard model, priority-plus overflow, and the states a clinical surface actually hits.

npm install @zoblocks/tabs
import { Tabs } from "@zoblocks/tabs";
import "@zoblocks/tabs/styles.css";

<Tabs
  as="tabs"
  variant="segmented"
  aria-label="Document scope"
  defaultValue="personal"
  items={[
    { value: "personal", label: "Personal", children: <PersonalDocs /> },
    { value: "shared", label: "Shared", count: 6, children: <SharedDocs /> },
  ]}
/>;

antd is an optional peer dependency — the package works without it, and @zoblocks/tabs-core has no dependency on React or antd at all.


The one thing worth knowing before you integrate

as is required, and there is no default

"Tabs" is four different components sharing a silhouette. They need four different accessibility trees, and shipping one of them and using it as all four is the most-reported tab defect in every design system audit:

| You have | as | Renders | Selected state | | -------------------- | -------------- | ---------------------------- | --------------------- | | Panels of one object | "tabs" | role="tablist" + buttons | aria-selected | | A list of URLs | "nav" | <nav> + real anchors | aria-current="page" | | A form value | "radiogroup" | role="radiogroup" + radios | aria-checked | | An ordered wizard | "steps" | role="tablist", gated | aria-selected |

The failure this prevents is specific and invisible. A role="tablist" wrapped around anchors announces "tab, 2 of 5"; the user presses → expecting to preview the next panel; the page navigates and their focus is destroyed. Every individual attribute is spelled correctly, so no automated checker — axe included — catches it.

variant is the orthogonal axis and it is free: any of the eleven skins can dress any of the four modes, because the skin is CSS and the mode is the accessibility tree.

Enforced rather than documented: validateTabsConfig throws on a tablist of links, and @zoblocks/tabs-semantic-mode makes it a lint error before the code ever runs.


Variants

segmented · underline · pill · enclosed · rail · ghost · stepper · command · card · stat · unstyled

The indicator resolves from the variant unless you override it: a thumb for segmented/command, a line for underline/rail, and none for the rest — wrapping variants have no continuous path for an indicator to travel along, and card/stat/ghost carry selection in their own background.

Overflow

Five strategies, none of which dominates, so the component exposes the choice. What it will not do is the industry default — let the strip run off the viewport and hope.

| overflow | Use when | | ---------------------- | ----------------------------------------------------------------------------------------------------------------- | | "scroll" (default) | Many tabs, order matters, touch-first. Fades, nudge buttons, scrollIntoView({block:"nearest"}). | | "menu" | Dense desktop tools where every tab must be listable. Measured priority-plus; the selected tab is pinned visible. | | "collapse" | Narrow containers. Becomes a native <select> — the only picker that already works on every phone. | | "wrap" | Pill filters only. Illegal on a tablist: once it wraps, "the next tab" stops being a direction. | | "none" | You have measured and they always fit. |

Three API layers

// 1 — declarative. The 90% case.
<Tabs as="tabs" items={items} aria-label="Chart" />

// 2 — compound, when you need the panel tree.
<Tabs.Root as="tabs" onBeforeChange={confirmDiscard}>
  <Tabs.List aria-label="Chart" extra={<Tabs.AddButton />}>
    <Tabs.Trigger value="labs" count={2} tone="critical">Labs</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panels>
    <Tabs.Panel value="labs" mount="lazy"><Labs /></Tabs.Panel>
  </Tabs.Panels>
</Tabs.Root>

// 3 — headless, when none of the skins fit.
const tabs = useTabs({ as: "radiogroup", items, value, onChange });
<div {...tabs.getListProps({ "aria-label": "Range" })}>
  {items.map((item, i) => <MyChip key={item.value} {...tabs.getTriggerProps(i)} />)}
</div>

Keyboard

| Key | tabs / steps | radiogroup | nav | | --------------------------------------------- | ------------------------------------------ | ---------------- | ----------------- | | Tab | Enters at the selected tab, then exits | Same | Visits every link | | → ← | Previous / next, wrapping (RTL-aware) | Same, and checks | Browser default | | ↑ ↓ | Vertical orientation only | Same | Browser default | | Home End | First / last | Same | — | | Enter Space | Activates under activation="manual" | Checks | Follows the link | | Delete Backspace | Closes a closable tab | — | — | | a–z, 0–9 | Typeahead, 600 ms buffer, repeats cycle | Same | — | | Ctrl+Shift+→ | Reorders, when editable.onReorder is set | — | — |

Use activation="manual" when a panel fetches. Under the default, arrowing across six tabs fires six requests and reads six live regions.

Colour is never the signal

count + tone reaches the accessible name as a word: a red 2 on a Labs tab announces "Labs, 2 critical", not "Labs 2". Same for dot ("unsaved changes") and availability ("showing cached data").

A disabled tab uses aria-disabled, never the disabled attribute, and disabledReason is mandatory — in a chart, "no behavioural health section" and "behavioural health, restricted" are different clinical facts, and a keyboard user must be able to discover which one they are looking at.

Theming

Every value resolves through --zb-tabs-* → --zb-* → --ant-* → a literal. That chain means the same stylesheet is correct in a ZoBlocks app, in a plain antd app driven by ConfigProvider (no JavaScript required), and on a bare page. Rebranding is four lines:

[data-brand="northgate"] {
  --zb-tabs-track-radius: 6px;
  --zb-tabs-thumb-radius: 3px;
  --zb-tabs-thumb-shadow: none;
  --zb-tabs-thumb-border: var(--zb-border-strong);
}

For a host on antd's non-cssVar theme, @zoblocks/tabs/antd exports <AntdTabsBridge>, which maps antd's JavaScript tokens onto the same surface.

What it will not do

  • Render a tablist of links — lint error, then a thrown error.
  • Wrap a tablist onto two rows. Use "menu" or "collapse".
  • Hide a disabled or empty tab. Both change the tab count between sessions.
  • Reorder tabs by importance. Position is memory.
  • Animate panel height. Panels swap; they do not grow.
  • Own routing. syncTo writes through an adapter you supply.
  • Enforce your permissions. disabled and availability are presentation; a hidden panel is not a security control.

ZoBlocks is not a compliance boundary. It does not make an application HIPAA, GDPR or DPDP compliant, and it is not a medical device or clinical decision support.