/

React Tabs: Pill, Underline and the Routing Question

Which indicator style fits which content, the roving-tabindex contract hand-built tabs skip, and when the active tab belongs in the URL.

Serafim Korablev
Serafim Korablev
@korablev

Tabs are the cheapest way to hide something you have decided is less important, which is why they are overused. The question that decides whether a set of tabs helps is not how they look: it is whether a reader ever needs two of the panels at the same time. If they do, tabs have turned one screen into a memory test.

236 public components on 21st carry the tabs tag. The visual patterns are settled; the two decisions that matter are which indicator style you pick and whether the tab belongs in the URL.

The underline and the pill

Two families, and the difference is not cosmetic. An underline sits under the label and works with any number of tabs of any width, which makes it the safe default for content. A pill wraps the active label in a filled shape, which reads more like a control and works best with three or four short options.

Vercel Tabs by yadwinder is the underline-with-a-sliding-indicator version, Animated Tabs by Chetan Verma and Animated Tabs by preetsuthar17 are the pill takes, and Slide Tabs by Le Thanh moves the highlight between them.

The sliding indicator is the detail worth getting right: it should animate its position and width, not fade out and in, and it should be measured from the active label rather than assumed to be equal-width, or the first long label breaks it.

The expandable set

Expandable Tabs by victorwelander is the most-bookmarked component in this family: icons that expand to show their label when selected. It is a good answer to a real problem - fitting eight destinations into a narrow bar - and it comes with the usual icon-only caveat, which is that every collapsed tab still needs an accessible name and a tooltip.

Vertical tabs

Vertical Tabs by 0xUrvish is the sidebar arrangement. Worth reaching for when the labels are long, when there are more than about six, or on a settings screen where the list is really navigation. At that point, ask whether they should be routes instead.

Filters that look like tabs

Gooey Filter by danielpetho and FAQ tabs by vaib215 use the tab shape for filtering rather than for switching panels. 3D Icon Tabs by Andrew Luo is the decorative end of the same idea.

The distinction matters for markup. A filter changes the contents of one list and belongs in a radiogroup or a set of toggle buttons; tabs switch between separate panels and use the tab roles. Using tab semantics for a filter makes a screen reader announce panels that do not exist.

The keyboard contract

Small, specific, and the part hand-built implementations skip.

The tab list is one stop in the tab order, not one stop per tab. Arrow keys move between tabs, Home and End jump to the ends, and the active tab is the only one with tabindex="0". That is a roving tabindex, and without it a bar of eight tabs adds eight stops between the reader and the content.

The roles are tablist, tab and tabpanel, wired with aria-controls and aria-selected, and the panel points back with aria-labelledby. The panel itself takes tabindex="0" so that Tab from the selected tab lands in the content rather than skipping past it.

One choice to make deliberately: automatic activation, where moving the arrow key switches the panel, or manual, where the reader presses Enter. Automatic is nicer for cheap panels; manual is correct when switching triggers a fetch, because otherwise arrowing through five tabs fires five requests.

Tabs and the URL

The decision most implementations never make. A tab held only in React state means the reader cannot link to it, the back button does something unrelated, a refresh loses their place, and a crawler sees only the first panel.

Put the active tab in the query string when the panels are content worth reaching: ?tab=pricing. Keep it in state when the tabs are a view preference, like a chart's time range. And if the panels are substantial enough that each deserves a title and a description, they are pages, not tabs.

Where else to look

The honest list of alternatives, because the answer is not always us:

SourceBest forTrade-off
shadcn/ui TabsThe roles, roving tabindex and activation mode already handledPlain; the sliding indicator is yours to add
Radix / Base UIThe same behaviour with no styling at allYou write every pixel
Real routesPanels with their own address, title and metadataMore plumbing, and a full navigation per switch
21stExpandable, vertical, gooey and sliding-indicator setsQuality varies by author, so preview before you take it

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue. The 21st MCP covers the same ground without leaving the editor: ask Claude, Cursor or Codex for tabs, get the previews inline, and let the agent write the file.

Browse tab components →

Frequently asked

When should you use tabs instead of separate pages?
When the reader never needs two panels at the same time and the panels are lightweight views of one thing. If a panel deserves its own title, description and address, it is a page. If the reader has to compare two panels, tabs have turned one screen into a memory test.
What is the keyboard contract for tabs?
One stop in the tab order for the whole list, not one per tab. Arrow keys move between tabs, Home and End jump to the ends, and only the active tab has tabindex="0". The panel takes tabindex="0" too, so Tab from the selected tab lands in the content. That roving tabindex is the part hand-built tab bars usually skip.
Should the active tab be in the URL?
If the panels are content worth reaching, yes, as a query parameter. A tab held only in React state cannot be linked, breaks the back button, loses its place on refresh, and shows a crawler only the first panel. Keep it in state when the tabs are a view preference, like a chart's time range.
Should tabs switch automatically as you arrow through them?
Automatic activation is nicer for cheap panels; manual activation, where the reader presses Enter, is correct when switching triggers a fetch. Otherwise arrowing across five tabs fires five requests. Pick deliberately rather than inheriting whichever the library defaults to.

Published

Aug 20, 2026

Read time

5 min

Tags

GuideTabsReactAccessibility

Share