Tabs
Account
Make changes to your account here.
Password
Change your password here.
<div class="tabs" style="width: 24rem"> <div class="tabs-list"> <button class="tabs-trigger" type="button" data-value="account">Account</button> <button class="tabs-trigger" type="button" data-value="password">Password</button> </div>
<div class="tabs-content"> <div class="card"> <div class="card-header"> <h3 class="card-title">Account</h3> <p class="card-description">Make changes to your account here.</p> </div> <div class="card-content"> <div class="field-group"> <div class="field"> <label class="label" for="tabs-name">Name</label> <input class="input" id="tabs-name" value="Ada Lovelace" /> </div> <div class="field"> <label class="label" for="tabs-username">Username</label> <input class="input" id="tabs-username" value="@ada" /> </div> </div> </div> <div class="card-footer"> <button class="btn">Save changes</button> </div> </div> </div>
<div class="tabs-content"> <div class="card"> <div class="card-header"> <h3 class="card-title">Password</h3> <p class="card-description">Change your password here.</p> </div> <div class="card-content"> <div class="field-group"> <div class="field"> <label class="label" for="tabs-current">Current password</label> <input class="input" id="tabs-current" type="password" /> </div> <div class="field"> <label class="label" for="tabs-new">New password</label> <input class="input" id="tabs-new" type="password" /> </div> </div> </div> <div class="card-footer"> <button class="btn">Save password</button> </div> </div> </div></div><div class="tabs"> <div class="tabs-list"> <button class="tabs-trigger" type="button">Account</button> <button class="tabs-trigger" type="button">Password</button> </div>
<div class="tabs-content">…</div> <div class="tabs-content">…</div></div>Triggers and panels are paired in order, so there are no ids to keep in sync. partialkit adds the
roles, the roving tab order and the arrow keys; write aria-controls yourself if the panels are
not siblings of the list.
| Class | Part |
|---|---|
.tabs | The wrapper |
.tabs-list | The row of triggers |
.tabs-trigger | One tab |
.tabs-content | Its panel |
| Attribute | On | Meaning |
|---|---|---|
data-variant="line" | .tabs-list | Underline instead of the raised pill |
data-orientation="vertical" | .tabs | List beside the panel, arrows move up and down |
data-activation="manual" | .tabs | Arrows move focus; Enter or Space selects |
data-active | .tabs-trigger | Which tab starts selected |
disabled | .tabs-trigger | Skipped by the arrow keys |
Variants
Section titled “Variants”Everything at a glance.
What happened lately.
How it all behaves.
<div class="tabs" style="width: 20rem"> <div class="tabs-list" data-variant="line"> <button class="tabs-trigger" type="button">Overview</button> <button class="tabs-trigger" type="button">Activity</button> <button class="tabs-trigger" type="button">Settings</button> </div>
<div class="tabs-content"><p>Everything at a glance.</p></div> <div class="tabs-content"><p>What happened lately.</p></div> <div class="tabs-content"><p>How it all behaves.</p></div></div>Vertical
Section titled “Vertical”Name, description and visibility.
Who can see and edit this project.
Plan, invoices and payment method.
<div class="tabs" data-orientation="vertical" style="width: 24rem"> <div class="tabs-list"> <button class="tabs-trigger" type="button">General</button> <button class="tabs-trigger" type="button">Members</button> <button class="tabs-trigger" type="button">Billing</button> </div>
<div class="tabs-content"><p>Name, description and visibility.</p></div> <div class="tabs-content"><p>Who can see and edit this project.</p></div> <div class="tabs-content"><p>Plan, invoices and payment method.</p></div></div>With icons
Section titled “With icons”Numbers, charts and the usual.
Everyone with access.
Not reachable.
<div class="tabs" style="width: 22rem"> <div class="tabs-list"> <button class="tabs-trigger" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect width="7" height="9" x="3" y="3" rx="1"></rect> <rect width="7" height="5" x="14" y="3" rx="1"></rect> <rect width="7" height="9" x="14" y="12" rx="1"></rect> <rect width="7" height="5" x="3" y="16" rx="1"></rect> </svg> Dashboard </button> <button class="tabs-trigger" type="button"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path> <circle cx="9" cy="7" r="4"></circle> <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path> </svg> Team </button> <button class="tabs-trigger" type="button" disabled> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect width="18" height="11" x="3" y="11" rx="2"></rect> <path d="M7 11V7a5 5 0 0 1 10 0v4"></path> </svg> Locked </button> </div>
<div class="tabs-content"><p>Numbers, charts and the usual.</p></div> <div class="tabs-content"><p>Everyone with access.</p></div> <div class="tabs-content"><p>Not reachable.</p></div></div>Events
Section titled “Events”Selecting a tab fires pk:tabs:change on the trigger, carrying data-value — enough to load a
panel’s contents on first open.
document.addEventListener("pk:tabs:change", (event) => { console.log(event.detail.value);});Set dir="rtl" on the page or on any subtree — the arrow keys follow the reading direction, so
Left moves forward. See the button page for a live example.
Accessibility
Section titled “Accessibility”- The list is a
tablist, each trigger atab, each panel atabpanel— applied for you, along witharia-selectedandaria-controls. - One stop in the tab order. Tab reaches the selected trigger, then the panel; arrows move between tabs. Tabbing through six triggers to reach the content is the mistake this pattern exists to avoid.
- Panels are focusable.
tabindex="0"means a panel with nothing but text can still be reached and scrolled by keyboard. - Automatic activation by default, which matches the platform and shadcn/ui. Switch to
data-activation="manual"when a panel is expensive to load, so arrowing past it costs nothing. - A disabled tab stays announced. It is skipped by the arrow keys but still read, which tells people it exists — unlike removing it.