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

react-multi-tab

v1.0.1

Published

A headless, accessible, router-agnostic multi-tab component library for React with TypeScript generics

Readme

react-multi-tab

A headless, accessible, router-agnostic, and fully type-safe multi-tab component library for React.

npm version License: MIT

Features

  • Agnostic & Pluggable: Works independently of any bundler or router. Includes adapters for memory, URL Search Params, and react-router-dom.
  • Headless & Accessible: Follows the WAI-ARIA Tabs pattern. Complete keyboard navigation (Arrow keys, Home, End) out of the box. You control the styling.
  • TypeScript Generics: Fully type-safe context, components, and hooks. Never use any again.
  • State Preservation: Keeps tab content mounted when inactive (via hidden attribute) to preserve form states and scroll positions.

🚀 Performance & Developer Experience (DX)

We have built the core state-management of react-multi-tab to meet the highest performance and Developer Experience (DX) standards.

1. useSyncExternalStore (The Performance Boost)

We stripped the large object out of React Context and replaced it with a custom Vanilla JS store (createMultiTabStore). By utilizing React 18's useSyncExternalStore, components like useTabData now strictly subscribe to only the data they care about.

Why it matters: Typing in an input field inside a specific tab will NO LONGER trigger a re-render across the entire Tab system. Only the component that called useTabData for that specific tab will update!

2. TabInstanceContext (The DX Boost)

Developers no longer need to manually pass down the cryptic instanceId to every page component. Because <TabPanels /> implicitly wraps your components with <TabContent>, it invisibly provides the context to all descendants.

// Inside any page (e.g., A-Page.tsx)
const [data, setData] = useTabData<APageData>(); // Boom! Magic.

useTabData automatically finds its own tab's context. If no context is found, it safely falls back to the globally active tab.

3. Built for Redux/Zustand Users

For teams that prefer keeping form data in a global Redux slice instead of our internal data store, we introduced the useTabInstanceId() hook.

import { useTabInstanceId } from 'react-multi-tab';

function MyReduxPage() {
  const tabId = useTabInstanceId(); // Seamlessly gets the ID!
  
  const handleChange = () => {
    dispatch(updateFormSlice({ tabId, data: '...' }));
  }
}

4. Smart Tab History

When a tab is closed, the library remembers the exact history of your active tabs and gracefully falls back to the previously active tab instead of abruptly jumping to the end of the list. Exactly like VS Code!

Installation

npm install react-multi-tab
# or
yarn add react-multi-tab

Basic Usage

Here is a full example showing how to build a tabbed layout using the vanilla URL searchParamsAdapter and the new explicit page registry.

1. Define Your Pages & Registry

Create your page components and register them explicitly.

// src/registry.ts
import { createPageRegistry, useTabData } from 'react-multi-tab';

interface DashboardData { filter?: string; }

function Dashboard({ instanceId }: { instanceId: string }) {
  // 100% Type-safe!
  const [data, setData] = useTabData<DashboardData>();
  
  return (
    <div>
      <h2>Dashboard</h2>
      <p>Filter: {data.filter ?? 'None'}</p>
      <button onClick={() => setData({ filter: 'Active' })}>Set Filter</button>
    </div>
  );
}

function Settings() {
  return <h2>Settings</h2>;
}

// Create a bundler-agnostic registry
export const registry = createPageRegistry([
  { id: 'dashboard', label: 'Dashboard', component: Dashboard },
  { id: 'settings', label: 'Settings', component: Settings },
]);

2. Wrap with Provider & Build the Layout

Use the provided headless components to build your accessible tab interface. They include all necessary ARIA attributes and keyboard events.

// src/App.tsx
import { 
  MultiTabProvider, 
  searchParamsAdapter,
  TabList, 
  TabTrigger, 
  TabCloseButton, 
  TabPanels,
  useMultiTab
} from 'react-multi-tab';
import { registry } from './registry';

function MainLayout() {
  const { tabs, openTab } = useMultiTab();

  return (
    <div style={{ display: 'flex' }}>
      {/* Sidebar / Menu */}
      <nav style={{ width: 200 }}>
        <button onClick={() => openTab('dashboard')}>Open Dashboard</button>
        <button onClick={() => openTab('settings')}>Open Settings</button>
      </nav>

      <div style={{ flex: 1 }}>
        {/* Accessible Tab List */}
        <TabList aria-label="My Application Tabs">
          {tabs.map((tab) => (
            <TabTrigger key={tab.instanceId} instanceId={tab.instanceId}>
              {tab.label}
              <TabCloseButton instanceId={tab.instanceId} />
            </TabTrigger>
          ))}
        </TabList>

        {/* Renders all active and hidden tab panels */}
        <TabPanels />
      </div>
    </div>
  );
}

export default function App() {
  return (
    <MultiTabProvider 
      registry={registry}
      adapter={searchParamsAdapter()} // Syncs active tabs to the browser URL
    >
      <MainLayout />
    </MultiTabProvider>
  );
}

Running the Playground

You can easily test the components locally using the built-in Playground!

  1. Go to the project root directory.
  2. Run the playground command:
yarn run dev:demo
  1. Open http://localhost:3000 to interact with the demo.

Advanced Features

React Router Integration

If you want to sync your tab state with React Router, you can use the optional adapter.

import { MultiTabProvider } from 'react-multi-tab';
import { useReactRouterAdapter } from 'react-multi-tab/adapters/react-router';

function App() {
  const routerAdapter = useReactRouterAdapter();

  return (
    <MultiTabProvider registry={registry} adapter={routerAdapter}>
      <MainLayout />
    </MultiTabProvider>
  );
}

License

MIT © Arif GEVENCİ