Tabs New
A set of label elements with hidden radio inputs inside
a .tabs container. No JavaScript. CSS
:has() handles panel switching.
Default
-
Each
labelwraps a hidden radioinput. Addcheckedto set the default tab.
CSS uses:has()to watch the checked state inside[role="tablist"]and show the panel at the matching position, with no JavaScript. -
Give the
headerrole="tablist"and anaria-label.
The tab bar styling targets[role="tablist"]directly. Screen readers also use this to announce the group and let users navigate between tabs. -
Connect each input to its panel with matching
idandaria-controls.
Lets assistive technology jump directly from a tab to its associated content. -
Mark each panel with
role="tabpanel",aria-labelledby, andtabindex="0".
All[role="tabpanel"]elements are hidden by default. The Nth panel is revealed when the Nth tab is checked.aria-labelledbynames the panel after its tab, andtabindex="0"makes it reachable by keyboard.
Manage your account settings and preferences.
Change your password and security settings.
Configure how and when you receive notifications.
<section class="tabs">
<header role="tablist" aria-label="Account settings">
<label>
<input
type="radio"
name="tabs"
id="tab-1"
checked
aria-controls="panel-1"
/>
Account
</label>
<label>
<input type="radio" name="tabs" id="tab-2" aria-controls="panel-2" />
Password
</label>
<label>
<input type="radio" name="tabs" id="tab-3" aria-controls="panel-3" />
Notifications
</label>
</header>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1" tabindex="0">
<p>Manage your account settings and preferences.</p>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" tabindex="0">
<p>Change your password and security settings.</p>
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3" tabindex="0">
<p>Configure how and when you receive notifications.</p>
</div>
</section>
Disabled tab
Add disabled to the radio input to disable a tab.
Overview content goes here.
Analytics content goes here.
Reports content goes here.
<section class="tabs">
<header role="tablist" aria-label="Dashboard">
<label>
<input
type="radio"
name="tabs"
id="tab-1"
checked
aria-controls="panel-1"
/>
Overview
</label>
<label>
<input type="radio" name="tabs" id="tab-2" aria-controls="panel-2" />
Analytics
</label>
<label>
<input
type="radio"
name="tabs"
id="tab-3"
disabled
aria-controls="panel-3"
/>
Reports
</label>
</header>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1" tabindex="0">
Overview content goes here.
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" tabindex="0">
Analytics content goes here.
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3" tabindex="0">
Reports content goes here.
</div>
</section>
With content
Tab panels can contain any content.
| Name | Role | Status |
|---|---|---|
| Alice | Engineer | Active |
| Bob | Designer | Away |
| Carol | Manager | Active |
No activity yet
Actions taken by your team will appear here.
<section class="tabs" style="display: grid; gap: 1rem">
<header role="tablist" aria-label="Team">
<label>
<input
type="radio"
name="tabs"
id="tab-1"
checked
aria-controls="panel-1"
/>
Members
</label>
<label>
<input type="radio" name="tabs" id="tab-2" aria-controls="panel-2" />
Activity
</label>
<label>
<input type="radio" name="tabs" id="tab-3" aria-controls="panel-3" />
Invite
</label>
</header>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1" tabindex="0">
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td>
<td>Engineer</td>
<td>Active</td>
</tr>
<tr>
<td>Bob</td>
<td>Designer</td>
<td>Away</td>
</tr>
<tr>
<td>Carol</td>
<td>Manager</td>
<td>Active</td>
</tr>
</tbody>
</table>
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" tabindex="0">
<section class="empty">
<svg><!-- icon --></svg>
<h3>No activity yet</h3>
<p>Actions taken by your team will appear here.</p>
</section>
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3" tabindex="0">
<form style="display: grid; gap: 1em">
<label class="field">
<span>Email address</span>
<input type="email" placeholder="colleague@example.com" />
</label>
<div class="field">
<span>Role</span>
<button type="button" class="outlined" popovertarget="role">
Member
</button>
<div id="role" popover>
<menu>
<li>
<label>
<input type="radio" name="role" value="member" checked /> Member
</label>
</li>
<li>
<label>
<input type="radio" name="role" value="admin" /> Admin
</label>
</li>
<li>
<label>
<input type="radio" name="role" value="viewer" /> Viewer
</label>
</li>
</menu>
</div>
</div>
<button type="submit">Send invite</button>
</form>
</div>
</section>
Nested
Tabs nest as deep as you like. Each level is independent, so switching the outer tabs leaves the inner ones where they were.
Outer panel one, holding another tabs component:
There's another tabs component in the other tab
Woof
Meow
Outer panel two.
<!-- Level 1 -->
<section class="tabs first">
<header role="tablist" aria-label="Outer tabs">
<label> <input type="radio" name="outer" checked /> Overview </label>
<label><input type="radio" name="outer" /> Settings</label>
</header>
<div role="tabpanel">
<p>Outer panel one, holding another tabs component:</p>
<!-- Level 2 -->
<section class="tabs second">
<header role="tablist" aria-label="Inner tabs">
<label><input type="radio" name="inner" /> Info</label>
<label> <input type="radio" name="inner" checked /> Animals </label>
</header>
<div role="tabpanel">
<p>There's another tabs component in the other tab</p>
</div>
<div role="tabpanel">
<!-- Level 3 -->
<section class="tabs third">
<header role="tablist" aria-label="Inner tabs">
<label> <input type="radio" name="third" checked /> Dog </label>
<label><input type="radio" name="third" /> Cat</label>
</header>
<div role="tabpanel"><p>Woof</p></div>
<div role="tabpanel"><p>Meow</p></div>
</section>
</div>
</section>
</div>
<div role="tabpanel">
<p>Outer panel two.</p>
</div>
</section>
<style>
.tabs {
padding: 1rem;
.first {
border: 1px solid var(--ui-neutral-100);
}
.second {
background-color: var(--ui-neutral-100);
}
.third {
background-color: var(--ui-neutral-0);
}
}
</style>