N
NJHTools

Tailwind CSS Tabs Components

Underline tabs, pill tabs and vertical tabs with proper ARIA roles and arrow-key navigation, plus a radio-button segmented control that needs no JavaScript.

4 components · live preview · copy as HTML or JSX · free for commercial use

4 tabs to copy

Underline Tabs

Tabs

Update your name, photo and public bio.

Accessible ARIA tabs: click or use the arrow, Home and End keys. Tailwind's aria-selected: variant styles the active tab, so the JS only flips attributes.

<div class="w-full max-w-md">
  <div role="tablist" aria-label="Account settings" class="flex gap-6 border-b border-slate-200">
    <button type="button" role="tab" id="tab-profile" aria-controls="panel-profile" aria-selected="true" class="-mb-px border-b-2 border-transparent px-1 pb-3 text-sm font-semibold text-slate-500 transition-colors hover:text-slate-700 aria-selected:border-indigo-600 aria-selected:text-indigo-600">Profile</button>
    <button type="button" role="tab" id="tab-security" aria-controls="panel-security" aria-selected="false" tabindex="-1" class="-mb-px border-b-2 border-transparent px-1 pb-3 text-sm font-semibold text-slate-500 transition-colors hover:text-slate-700 aria-selected:border-indigo-600 aria-selected:text-indigo-600">Security</button>
    <button type="button" role="tab" id="tab-billing" aria-controls="panel-billing" aria-selected="false" tabindex="-1" class="-mb-px border-b-2 border-transparent px-1 pb-3 text-sm font-semibold text-slate-500 transition-colors hover:text-slate-700 aria-selected:border-indigo-600 aria-selected:text-indigo-600">Billing</button>
  </div>
  <div role="tabpanel" id="panel-profile" aria-labelledby="tab-profile" tabindex="0" class="pt-4 text-sm text-slate-600">Update your name, photo and public bio.</div>
  <div role="tabpanel" id="panel-security" aria-labelledby="tab-security" tabindex="0" class="pt-4 text-sm text-slate-600" hidden>Change your password and turn on two-factor authentication.</div>
  <div role="tabpanel" id="panel-billing" aria-labelledby="tab-billing" tabindex="0" class="pt-4 text-sm text-slate-600" hidden>Manage your plan, payment method and invoices.</div>
</div>

Pill Tabs

Tabs

1,284 visits this week

Same tab script as the Underline Tabs — one copy handles every tablist on the page.

<div class="w-full max-w-md">
  <div role="tablist" aria-label="Report period" class="inline-flex gap-1 rounded-full bg-slate-100 p-1">
    <button type="button" role="tab" id="tab-week" aria-controls="panel-week" aria-selected="true" class="rounded-full px-4 py-1.5 text-sm font-semibold text-slate-600 transition-colors hover:text-slate-900 aria-selected:bg-indigo-600 aria-selected:text-white aria-selected:shadow-sm">Week</button>
    <button type="button" role="tab" id="tab-month" aria-controls="panel-month" aria-selected="false" tabindex="-1" class="rounded-full px-4 py-1.5 text-sm font-semibold text-slate-600 transition-colors hover:text-slate-900 aria-selected:bg-indigo-600 aria-selected:text-white aria-selected:shadow-sm">Month</button>
    <button type="button" role="tab" id="tab-year" aria-controls="panel-year" aria-selected="false" tabindex="-1" class="rounded-full px-4 py-1.5 text-sm font-semibold text-slate-600 transition-colors hover:text-slate-900 aria-selected:bg-indigo-600 aria-selected:text-white aria-selected:shadow-sm">Year</button>
  </div>
  <div role="tabpanel" id="panel-week" aria-labelledby="tab-week" tabindex="0" class="mt-4 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600"><span class="text-2xl font-bold text-slate-900">1,284</span> visits this week</div>
  <div role="tabpanel" id="panel-month" aria-labelledby="tab-month" tabindex="0" class="mt-4 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600" hidden><span class="text-2xl font-bold text-slate-900">5,902</span> visits this month</div>
  <div role="tabpanel" id="panel-year" aria-labelledby="tab-year" tabindex="0" class="mt-4 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600" hidden><span class="text-2xl font-bold text-slate-900">61,340</span> visits this year</div>
</div>

Vertical Tabs

Tabs

Create a workspace, invite your team and connect your first data source.

A side list on wider screens, a scrollable row on mobile. Arrow keys move between tabs in both layouts.

<div class="flex w-full max-w-lg flex-col gap-4 sm:flex-row">
  <div role="tablist" aria-label="Help topics" class="flex shrink-0 gap-1 overflow-x-auto sm:w-40 sm:flex-col">
    <button type="button" role="tab" id="tab-start" aria-controls="panel-start" aria-selected="true" class="whitespace-nowrap rounded-lg px-3 py-2 text-left text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 aria-selected:bg-indigo-50 aria-selected:text-indigo-700">Getting started</button>
    <button type="button" role="tab" id="tab-account" aria-controls="panel-account" aria-selected="false" tabindex="-1" class="whitespace-nowrap rounded-lg px-3 py-2 text-left text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 aria-selected:bg-indigo-50 aria-selected:text-indigo-700">Account</button>
    <button type="button" role="tab" id="tab-api" aria-controls="panel-api" aria-selected="false" tabindex="-1" class="whitespace-nowrap rounded-lg px-3 py-2 text-left text-sm font-medium text-slate-600 transition-colors hover:bg-slate-100 aria-selected:bg-indigo-50 aria-selected:text-indigo-700">API access</button>
  </div>
  <div role="tabpanel" id="panel-start" aria-labelledby="tab-start" tabindex="0" class="flex-1 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600">Create a workspace, invite your team and connect your first data source.</div>
  <div role="tabpanel" id="panel-account" aria-labelledby="tab-account" tabindex="0" class="flex-1 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600" hidden>Edit your profile, notification settings and connected apps.</div>
  <div role="tabpanel" id="panel-api" aria-labelledby="tab-api" tabindex="0" class="flex-1 rounded-xl border border-slate-200 bg-white p-4 text-sm text-slate-600" hidden>Generate API keys and read the rate-limit guidelines.</div>
</div>

Segmented Control

Tabs

Calendar view

CSS-only: a radio group styled with peer-checked:, so it submits with a form and works with the keyboard out of the box.

<fieldset class="inline-flex rounded-xl bg-slate-100 p-1">
  <legend class="sr-only">Calendar view</legend>
  <label class="cursor-pointer">
    <input type="radio" name="calendar-view" value="day" class="peer sr-only" />
    <span class="block rounded-lg px-4 py-1.5 text-sm font-semibold text-slate-500 transition-colors peer-checked:bg-white peer-checked:text-slate-900 peer-checked:shadow-sm peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500">Day</span>
  </label>
  <label class="cursor-pointer">
    <input type="radio" name="calendar-view" value="week" class="peer sr-only" checked />
    <span class="block rounded-lg px-4 py-1.5 text-sm font-semibold text-slate-500 transition-colors peer-checked:bg-white peer-checked:text-slate-900 peer-checked:shadow-sm peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500">Week</span>
  </label>
  <label class="cursor-pointer">
    <input type="radio" name="calendar-view" value="month" class="peer sr-only" />
    <span class="block rounded-lg px-4 py-1.5 text-sm font-semibold text-slate-500 transition-colors peer-checked:bg-white peer-checked:text-slate-900 peer-checked:shadow-sm peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500">Month</span>
  </label>
</fieldset>

How the tabs work

Each tab set follows the WAI-ARIA tabs pattern: a role="tablist" containing role="tab" buttons, each pointing at its role="tabpanel" with aria-controls. Inactive panels carry the hidden attribute. The short vanilla script in the JS tab handles clicks plus the Arrow, Home and End keys, and only toggles aria-selected, tabindex and hidden — the styling comes from Tailwind's aria-selected: variant, so there are no class lists to keep in sync. One copy of the script powers every tab set on the page.

Choose the style by context: underline tabs for page sections and settings, pill tabs for switching a data view, vertical tabs for help centers and long settings screens. The segmented control is a styled radio group — ideal for a compact either/or choice inside a form.

Tabs in React

In React, drop the script and keep the selected index in useState. Render aria-selected and tabIndex from that state, and use the hidden attribute on inactive panels so their content stays in the DOM for find-in-page and preserves form input when users switch tabs.

← Back to the full UI Component Library

Frequently Asked Questions

Can I make tabs with only CSS?↓

A segmented control built from radio inputs and peer-checked: styles works without JavaScript, as shown on this page. Full ARIA tabs with arrow-key navigation need a few lines of JavaScript to move focus and update aria-selected.

How do I style the active tab in Tailwind?↓

Use the aria-selected: variant, for example aria-selected:border-indigo-600 aria-selected:text-indigo-600. The active style then follows the aria-selected attribute automatically.

Should tabs change the URL?↓

If each tab holds substantial, linkable content, store the active tab in the URL hash or a query parameter so it can be shared and survives a reload. For small view switches, local state is enough.

Last updated: