Tabs
An accessible tab group with roving tabindex, full keyboard navigation, and zero visual opinion.
Basic Tabs
Click a tab or use Left / Right arrow keys, Home, and End to navigate. Disabled tabs are skipped automatically.
ARIA roles, aria-selected, aria-controls, and aria-labelledby are wired automatically.
Account
Manage your account settings and preferences.
Profile
Update your public profile information.
Notifications
Choose what alerts you'd like to receive.
<BitBhTabGroup class="tabs">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">Account</BitBhTab>
<BitBhTab class="tab">Profile</BitBhTab>
<BitBhTab class="tab">Notifications</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel">
<h3>Account</h3>
<p>Manage your account settings and preferences.</p>
</BitBhTabPanel>
<BitBhTabPanel class="tab-panel">
<h3>Profile</h3>
<p>Update your public profile information.</p>
</BitBhTabPanel>
<BitBhTabPanel class="tab-panel">
<h3>Notifications</h3>
<p>Choose what alerts you'd like to receive.</p>
</BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
.tab[data-disabled] { opacity: 0.4; cursor: not-allowed; }
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}Vertical Orientation
Set Vertical="true" to render tabs in a column.
Arrow-key navigation switches to Up / Down, and the root and tablist
both emit data-orientation="vertical" for CSS layout hooks.
General
Application-wide settings.
Security
Two-factor auth, sessions, and audit log.
Integrations
Connect third-party services.
<BitBhTabGroup Vertical="true" class="tabs tabs-vertical">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">General</BitBhTab>
<BitBhTab class="tab">Security</BitBhTab>
<BitBhTab class="tab">Integrations</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel">
<h3>General</h3>
<p>Application-wide settings.</p>
</BitBhTabPanel>
<BitBhTabPanel class="tab-panel">
<h3>Security</h3>
<p>Two-factor auth, sessions, and audit log.</p>
</BitBhTabPanel>
<BitBhTabPanel class="tab-panel">
<h3>Integrations</h3>
<p>Connect third-party services.</p>
</BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
.tab[data-disabled] { opacity: 0.4; cursor: not-allowed; }
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}
/* When Vertical="true" the root and tablist receive data-orientation="vertical",
which we use to flip the layout. */
.tabs[data-orientation="vertical"] {
flex-direction: row;
align-items: flex-start;
gap: 1.5rem;
}
.tabs[data-orientation="vertical"] .tab-list {
flex-direction: column;
border-bottom: none;
border-right: 1px solid #e4e4e7;
min-width: 12rem;
}
.tabs[data-orientation="vertical"] .tab {
border-bottom: none;
border-right: 2px solid transparent;
text-align: start;
}
.tabs[data-orientation="vertical"] .tab[data-state="active"] {
border-right-color: #2563eb;
border-bottom-color: transparent;
}
.tabs[data-orientation="vertical"] .tab-panels { flex: 1 1 auto; }Manual Activation
With Manual="true" arrow keys only move keyboard focus; press
Space or Enter to activate the focused tab. Useful when activating a tab is
expensive (loads data, navigates, etc.) — try it: focus a tab, then arrow
without selecting.
Inbox content
Sent content
Drafts content
Archive content
<BitBhTabGroup Manual="true" class="tabs">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">Inbox</BitBhTab>
<BitBhTab class="tab">Sent</BitBhTab>
<BitBhTab class="tab">Drafts</BitBhTab>
<BitBhTab class="tab">Archive</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel"><p>Inbox content</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Sent content</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Drafts content</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Archive content</p></BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>
@* Manual="true": arrow keys move focus only.
Press Space or Enter to activate the focused tab. *@/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
.tab[data-disabled] { opacity: 0.4; cursor: not-allowed; }
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}
/* Manual="true" changes only behavior (arrow keys move focus; Space/Enter
activates) — the same data-state="active" hook drives the styling. */Disabled Tab
Set Disabled="true" on individual tabs. Arrow-key navigation
skips disabled tabs, and the disabled tab still receives data-disabled
for CSS hooks.
Free plan features.
Pro plan features.
Enterprise plan features.
<BitBhTabGroup class="tabs">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">Free</BitBhTab>
<BitBhTab class="tab">Pro</BitBhTab>
<BitBhTab class="tab" Disabled="true">Enterprise (coming soon)</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel"><p>Free plan features.</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Pro plan features.</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Enterprise plan features.</p></BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
/* Hooks emitted when a tab is disabled.
Arrow-key navigation skips disabled tabs automatically — no JS needed. */
.tab[data-disabled] {
opacity: 0.4;
cursor: not-allowed;
}
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}Render Context (state-driven content)
BitBhTab exposes a BitBhTabRenderContext via
Context="t" so you can render badges, icons, or active
indicators driven by the tab's own state.
Direct messages
Mentions of you
Everything together
<BitBhTabGroup class="tabs">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">
<Template Context="t">
<span>Messages</span>
<span class="badge @(t.IsSelected ? "badge-active" : "")">12</span>
</Template>
</BitBhTab>
<BitBhTab class="tab">
<Template Context="t">
<span>Mentions</span>
<span class="badge @(t.IsSelected ? "badge-active" : "")">3</span>
</Template>
</BitBhTab>
<BitBhTab class="tab">
<Template Context="t">
<span>All</span>
</Template>
</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel"><p>Direct messages</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Mentions of you</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Everything together</p></BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
.tab[data-disabled] { opacity: 0.4; cursor: not-allowed; }
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}
/* The Tab render-prop context exposes IsSelected so you can style children
based on the parent tab's state. */
.badge {
margin-left: 0.5rem;
padding: 0.05rem 0.4rem;
background: #e4e4e7;
color: #52525b;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
}
.badge-active {
background: #2563eb;
color: #fff;
}Controlled
Drive selection externally with SelectedIndex and
OnSelectedIndexChange. The buttons below select tabs programmatically.
First panel
Second panel
Third panel
<div style="display:flex; flex-direction:column; gap:1rem; width:100%">
<div class="demo-row">
@for (var i = 0; i < 3; i++)
{
var captured = i;
<BitBhButton OnClick="() => selectedIndex = captured"
class="@(selectedIndex == captured ? "btn btn-primary" : "btn")">
Select tab @(captured + 1)
</BitBhButton>
}
<span class="demo-output">SelectedIndex: <strong>@selectedIndex</strong></span>
</div>
<BitBhTabGroup SelectedIndex="@selectedIndex"
OnSelectedIndexChange="i => selectedIndex = i"
class="tabs">
<BitBhTabList class="tab-list">
<BitBhTab class="tab">Tab 1</BitBhTab>
<BitBhTab class="tab">Tab 2</BitBhTab>
<BitBhTab class="tab">Tab 3</BitBhTab>
</BitBhTabList>
<BitBhTabPanels class="tab-panels">
<BitBhTabPanel class="tab-panel"><p>First panel</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Second panel</p></BitBhTabPanel>
<BitBhTabPanel class="tab-panel"><p>Third panel</p></BitBhTabPanel>
</BitBhTabPanels>
</BitBhTabGroup>
</div>
@code {
private int selectedIndex;
}/* All styles are consumer-authored — the library ships zero CSS */
.tabs {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.tab-list {
display: flex;
border-bottom: 1px solid #e4e4e7;
gap: 0.25rem;
}
.tab {
all: unset;
padding: 0.6rem 1rem;
font-size: 0.9rem;
font-weight: 500;
color: #71717a;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 150ms ease, border-color 150ms ease;
box-sizing: border-box;
}
.tab:hover { color: #18181b; }
.tab:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; border-radius: 0.25rem; }
/* Hooks emitted by BitBhTab */
.tab[data-state="active"] { color: #2563eb; border-bottom-color: #2563eb; }
.tab[data-disabled] { opacity: 0.4; cursor: not-allowed; }
.tab-panel {
padding: 0.5rem 0;
color: #18181b;
}
/* Controlled mode adds no new CSS hooks — data-state simply reflects whatever
SelectedIndex value you bind to. The buttons below drive selection. */
.demo-row { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.demo-output { font-size: 0.85rem; color: #71717a; }
.btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.9rem;
border: 1px solid #e4e4e7;
border-radius: 0.5rem;
background: #fff;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
}
.btn:hover { background: #f4f4f5; }
.btn-primary {
background: #2563eb;
border-color: #2563eb;
color: #fff;
}
.btn-primary:hover { background: #1d4ed8; border-color: #1d4ed8; }