react-tabs: Build Accessible, Customizable Tab Interfaces in React
Tabs are a deceptively simple UI pattern: the idea is obvious, but making tabs robust, accessible, and flexible takes thought. The react-tabs library gives you ARIA-friendly primitives and sensible defaults so you can focus on UX rather than re-implementing keyboard and accessibility logic.
Below you’ll find concise, production-oriented examples: getting started, controlled vs uncontrolled patterns, keyboard navigation, customization, and hardening for real apps. If you want a deep example of advanced tab interfaces, see this walkthrough: react-tabs tutorial and advanced patterns.
For package and API reference, the npm and GitHub pages are excellent quick references: react-tabs on npm and react-tabs GitHub.
Why choose react-tabs?
react-tabs is intentionally minimal: it provides accessible tab primitives (TabList, Tab, TabPanel) and handles the ARIA wiring and keyboard interaction for you. If you need a tab component that works well with screen readers and supports keyboard navigation out of the box, this library is a pragmatic choice.
Because it’s unopinionated about styling, you can use CSS, CSS-in-JS, or utility classes (Tailwind, etc.) to make tabs match your design system. That separation of concerns (behavior vs presentation) reduces long-term maintenance costs.
Finally, react-tabs supports both uncontrolled (internal state) and controlled patterns (external state). That makes it flexible enough for simple static pages as well as complex single-page apps where tab state might be driven by URL, Redux, or Context.
Getting started and installation
Install the package via npm or yarn. This is all you need to start using basic tabs.
npm install react-tabs
# or
yarn add react-tabs
Then import the components and render a basic tab interface:
import { Tabs, TabList, Tab, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
function Example() {
return (
<Tabs>
<TabList>
<Tab>First</Tab>
<Tab>Second</Tab>
</TabList>
<TabPanel>Content for first</TabPanel>
<TabPanel>Content for second</TabPanel>
</Tabs>
);
}
The default stylesheet included by the package is intentionally minimal. You can drop it in for quick prototypes and then replace or extend it with your own styles for production.
When you need a guided tutorial to build advanced interfaces (lazy panels, persisted state, nested tabs), check this advanced tutorial: Advanced Tab Interfaces with react-tabs.
Controlled vs Uncontrolled tabs
react-tabs supports both uncontrolled and controlled modes. Uncontrolled mode is easiest for static UIs—react-tabs manages the selected index for you. Controlled mode is required when tab state must be synchronized with URL, a global store, or other components.
Controlled usage looks like this: you keep the selectedIndex in state and update it via onSelect. This pattern is ideal if you want to persist the selected tab to localStorage, reflect it in the URL, or share it across components.
function ControlledTabs() {
const [index, setIndex] = React.useState(0);
return (
<Tabs selectedIndex={index} onSelect={i => setIndex(i)}>
<TabList> ... </TabList>
<TabPanel> ... </TabPanel>
</Tabs>
);
}
Uncontrolled mode is perfectly fine for many cases and simplifies wiring. Use controlled tabs when the tab index is part of your app’s state model or when implementing deep-linking and server-rendered snapshots.
Accessibility and keyboard navigation
Accessibility is not optional. react-tabs wires up ARIA roles, attributes, and keyboard handling (Arrow keys, Home/End, Tab focus behavior) by default. That removes a large accessibility burden and helps your product pass basic assistive-technology tests.
Keyboard navigation conventions you get out-of-the-box: left/right or up/down to move between tabs, Home and End to go to the first/last tab, and Enter/Space to activate a tab if your UX separates focus from activation. These behaviors follow WAI-ARIA Authoring Practices for tabs.
If you customize tab markup heavily, ensure you preserve the generated roles and aria-controls relationships. You can inspect markup in devtools or test with screen-reader simulators. For explicit keyboard control, you can still intercept onKeyDown on Tab components and augment behavior as needed.
Customization: styling, lazy loading, and ARIA tweaks
react-tabs intentionally leaves styles to you. The built-in stylesheet is a starting point; you can override classes or render your own markup and pass className props. Common customization points include active tab styling, animations for panels, and responsive layout for narrow viewports.
- className props: add classes to Tabs, TabList, Tab, and TabPanel
- lazy rendering: conditionally render heavy content inside TabPanel (e.g., charts) to improve initial render
- ARIA tweaks: preserve roles but update accessible labels for complex UIs
When lazily mounting panels, prefer pattern where heavy components mount only when the tab is selected. That avoids long initial loads and reduces work for hydration on server-rendered apps.
For animations, mount/unmount decisions and CSS transitions matter: using opacity transforms instead of layout-changing transitions usually gives the smoothest perceived performance.
Examples and patterns you’ll use in production
Pattern: deep-linking tabs. Synchronize selectedIndex with the URL query or hash so users can share a link to a specific tab. Keep an index-to-key map (or use stable keys) to avoid breakage when tabs are reordered.
Pattern: nested tabs. If you have nested tab panels, ensure keyboard handling and focus management don’t conflict. Limit scope of arrow-key handlers to the nearest TabList by using event handlers provided by react-tabs and avoid global key listeners unless necessary.
Pattern: server-side rendering (SSR). For SSR, render with a consistent initial selectedIndex and avoid client-only diffs. If you must hydrate tabs lazily, ensure content keys and the DOM tree shape remain stable between server and client.
Troubleshooting and common pitfalls
Common issue: mismatched Tab and TabPanel counts. If you render a TabList with 3 Tab components but only 2 TabPanel components, accessibility relationships break and selection logic may misbehave. Always keep them in sync or conditionally render both together.
Common issue: styling resets. If you import the default CSS for quick development and then add a design system that also targets the same classes, you might see unexpected visual regressions. Replace default styles early in the project lifecycle to avoid surprises.
Common issue: focus loss after DOM updates. If panels unmount and remount frequently, focus can move unexpectedly. Preserve DOM identity (keys) where appropriate or manage focus explicitly after rendering transitions.
Performance tips
Keep tab panels as lightweight as possible. If a panel hosts a chart or heavy tree, mount it lazily or use virtualization inside the panel. That reduces CPU and memory pressure when a page mounts with many tabs.
Memoize expensive children with React.memo or use lazy-loaded components (React.lazy + Suspense) to defer loading JS until the panel is shown. For code-splitting, load panel-specific bundles only when necessary.
Measure with DevTools and Lighthouse. Tabs themselves are cheap; the real cost is the content inside panels. Optimize images, avoid unnecessary re-renders, and ensure that layout thrashing is minimized during tab transitions.
Semantic core (keyword clusters)
Primary:
- react-tabs
- React tab component
- react-tabs tutorial
- react-tabs installation
- React accessible tabs
Secondary:
- React tab interface
- react-tabs example
- React tab navigation
- react-tabs setup
- React controlled tabs
- react-tabs customization
- React tab library
Clarifying / LSI / Related phrases:
- react tabs keyboard navigation
- react tab panels
- react-tabs getting started
- accessible tabs react
- tabs aria roles
- tablist tabpanel react
- lazy-loaded tab panels
FAQ
How do I install and set up react-tabs?
Install via npm or yarn: npm install react-tabs or yarn add react-tabs. Import the components (Tabs, TabList, Tab, TabPanel) and optionally include react-tabs/style/react-tabs.css for a quick baseline. Render the components in order: one TabList with Tab children and a TabPanel for each Tab.
How do I implement keyboard navigation and accessibility with react-tabs?
react-tabs provides ARIA roles and keyboard handlers by default (Arrow keys, Home/End). Use the library’s Tab and TabPanel primitives and avoid replacing generated roles. If you customize markup, ensure role="tablist", role="tab", and role="tabpanel" semantics and aria-controls/id relationships remain intact.
Should I use controlled or uncontrolled tabs in react-tabs?
Use uncontrolled tabs for simple local UIs: react-tabs manages selectedIndex for you. Use controlled tabs (pass selectedIndex and onSelect) when tab state must be synchronized with the URL, server-side state, or global stores. Controlled mode gives you predictable integration points for deep-linking and persistence.