Sidebar
A sidebar is a column of navigation links at the side of an app or docs site. It can collapse out of view or down to a rail of icons.
<tk-sidebar label="Documentation">
<tk-sidebar-nav-item href="/overview" current>Overview</tk-sidebar-nav-item>
<tk-sidebar-nav-item href="/install">Installation</tk-sidebar-nav-item>
<tk-collapsible open>
<span slot="trigger">Foundations</span>
<tk-sidebar-nav-item href="/color">Color</tk-sidebar-nav-item>
<tk-sidebar-nav-item href="/type">Typography</tk-sidebar-nav-item>
</tk-collapsible>
</tk-sidebar>Anatomy
| Part | What it is |
|---|---|
tk-sidebar |
the panel, with three regions: header, content, footer |
header slot |
app chrome above the links, such as a logo or search |
| content | the links; a <nav> landmark that scrolls on its own |
footer slot |
app chrome below the links, such as the account menu |
tk-sidebar-nav-item |
one row: a link, or a row that opens nested rows |
Build the rest from other components: a group is a
Rows
<tk-sidebar label="Workspace">
<tk-sidebar-nav-item href="/inbox"><tk-icon slot="icon" name="mail"></tk-icon>Inbox</tk-sidebar-nav-item>
<tk-sidebar-nav-item href="/starred" current><tk-icon slot="icon" name="star"></tk-icon>Starred</tk-sidebar-nav-item>
<tk-sidebar-nav-item open>
<tk-icon slot="icon" name="bookmark"></tk-icon>Projects
<tk-sidebar-nav-item slot="sub" href="/apollo">Apollo</tk-sidebar-nav-item>
<tk-sidebar-nav-item slot="sub" href="/borealis">Borealis</tk-sidebar-nav-item>
</tk-sidebar-nav-item>
<tk-sidebar-nav-item href="/archive" disabled>Archive</tk-sidebar-nav-item>
</tk-sidebar>current: marks the page people are on.iconslot: a before the label. The rail shows only this.tk-icontrailingslot: a countBadge or a "more" button after the label.subslot: nested rows. The row becomes a button that opens them in place, pushing the rows below down. A row with nested rows isn't a link, so leave outhref.disabled: dimmed and out of the Tab order.
Collapsing
collapsible |
When collapsed |
|---|---|
offcanvas (default) |
the panel slides away |
icon |
the panel narrows to a rail of icons, 56px wide |
none |
never collapses |
Collapsed rows leave the Tab order and the accessibility tree, so no one can tab into a hidden panel.
The icon rail
The rail shows top-level rows as their icons. Each row keeps its name for screen readers.
Anything in the content that isn't a row is hidden, including group captions, search
fields, and wrappers around rows, so a sidebar with groups should collapse offcanvas
instead. Pick icons that are clear without their labels.
<tk-sidebar id="rail" label="Main" collapsible="icon">
<tk-sidebar-nav-item href="/inbox"><tk-icon slot="icon" name="mail"></tk-icon>Inbox</tk-sidebar-nav-item>
<tk-sidebar-nav-item href="/starred"><tk-icon slot="icon" name="star"></tk-icon>Starred</tk-sidebar-nav-item>
</tk-sidebar>
<script>
document.getElementById('rail').open = false;
</script>open is on by default; to start collapsed, set the property from script, as above.
The toggle
There is no built-in toggle button. Call toggle() from your own
<tk-icon-button label="Toggle sidebar" onclick="document.getElementById('nav').toggle()">
<tk-icon name="menu"></tk-icon>
</tk-icon-button>Cmd + B on Apple platforms and Ctrl + B elsewhere toggle the sidebar from anywhere
on the page, except while typing in a field. Change the shortcut with hotkey, or turn it
off with hotkey="". persist remembers whether the sidebar was open, in localStorage
under tk-sidebar:open.
Variants
<tk-sidebar label="Settings" variant="floating"> <tk-sidebar-nav-item href="/profile" current>Profile</tk-sidebar-nav-item> <tk-sidebar-nav-item href="/billing">Billing</tk-sidebar-nav-item> </tk-sidebar>
sidebar(default): flush to the edge of the window, with a hairline on its inner side.floating: an inset card with rounded corners and a shadow.
side="right" pins it to the right edge.
Page layout
The sidebar doesn't move your page content. In a flex row beside your content, it needs nothing: the page reflows as the sidebar narrows. If you position it yourself, offset your content with the same custom properties the sidebar reads:
.app { --tk-sidebar-width: 260px; --tk-sidebar-rail-width: 56px; }
.app main { margin-inline-start: var(--tk-sidebar-width); }
.app tk-sidebar:not([open]) ~ main { margin-inline-start: 0; }
.app tk-sidebar[collapsible='icon']:not([open]) ~ main { margin-inline-start: var(--tk-sidebar-rail-width); }Mobile
A navigation drawer on a narrow screen is modal, so use a
<tk-icon-button label="Open navigation" onclick="document.getElementById('drawer').show()">
<tk-icon name="menu"></tk-icon>
</tk-icon-button>
<tk-sheet id="drawer" heading="Menu" side="left">
<tk-sidebar label="Main" collapsible="none">
<tk-sidebar-nav-item href="/home" current>Home</tk-sidebar-nav-item>
</tk-sidebar>
</tk-sheet>When to use
- Use a sidebar for main navigation in a tall column beside the page.
- Use a
Navigation menu for a horizontal row across the top. - Use a
Menu for actions. A sidebar row goes somewhere; it never does something. - Use a
Tree when people select or explore items, such as files. - Use a
Sheet on narrow screens. SeeMobile .
Content
- Name each row for its page, in sentence case: "Billing", "Team members".
- Keep labels short. A long label is cut off with an ellipsis.
- Name the landmark without the word "navigation":
label="Main", not "Main navigation". Screen readers already say "navigation".
Do / Don't
| ✅ Do | ❌ Don't |
|---|---|
| Put links in the rows | Put actions in the rows; use a menu |
Give each <nav> its own label |
Put "navigation" in the label |
Collapse a grouped sidebar offcanvas |
Collapse a grouped sidebar to a rail |
| Use a Sheet on narrow screens | Collapse the sidebar as a mobile drawer |
| Put rows straight into the sidebar for the rail | Wrap rail rows in another element; the rail hides it |
Related
Navigation menu : main navigation across the top.Collapsible : a group of rows.Sheet : the drawer on narrow screens.Tree : items people select or explore.Menu : actions.
API
tk-sidebar
| Attribute | Type | Default | What it does |
|---|---|---|---|
open |
boolean | true |
expanded or collapsed |
collapsible |
offcanvas | icon | none |
offcanvas |
how it collapses |
variant |
sidebar | floating |
sidebar |
flush, or an inset card |
side |
left | right |
left |
the edge it sits on |
label |
string | Main |
the <nav> landmark's name |
hotkey |
string | Mod+B |
the keyboard toggle; Mod is Cmd on Apple platforms, Ctrl elsewhere; "" turns it off |
persist |
boolean | false |
remember open in localStorage |
| Slot | Content |
|---|---|
header |
app chrome above the links |
| default | rows, groups, and other navigation content |
footer |
app chrome below the links |
toggle() opens or closes the sidebar.
tk-sidebar-nav-item
| Attribute | Type | Default | What it does |
|---|---|---|---|
href |
string | — | makes the row a link; ignored when the row has nested rows |
current |
boolean | false |
the page people are on; sets aria-current="page" |
open |
boolean | false |
whether the nested rows are shown |
disabled |
boolean | false |
dimmed, out of the Tab order |
| Slot | Content |
|---|---|
| default | the label, required; it is the accessible name |
icon |
one tk-icon before the label |
trailing |
a badge or a "more" button; hidden in the rail |
sub |
nested tk-sidebar-nav-item rows |
Events
tk-toggle fires before the sidebar opens or closes, with { open } set to the new state.
Cancel it to control open yourself. A row with nested rows fires it too, and so does a
Collapsible group, and both bubble up through the sidebar, so check the target:
sidebar.addEventListener('tk-toggle', (e) => {
if (e.target !== sidebar) return;
save(e.detail.open);
});CSS
tk-sidebar::part(panel),::part(header),::part(content),::part(footer): the panel and its three regions.tk-sidebar-nav-item::part(item),::part(icon),::part(label),::part(trailing),::part(panel): the row, its slots' wrappers, and the nested rows.--tk-sidebar-width: the open width;260pxby default.--tk-sidebar-rail-width: the rail width;56pxby default.
If you restyle the label, don't hide it with display: none in the rail: that removes the
row's name.
Tokens
| Part | Tokens |
|---|---|
| panel | surface/raised; border/subtle on the inner edge |
| floating panel | surface/raised + shadow-2, radius-16, space-8 from the edges; border/subtle in high contrast only |
| row | text/default, Body Small (14/20), radius-8, space-4 / space-8 padding, 32px min height |
| row, hover / press | state/hover / state/press |
| current row | state/on + text/accent, medium weight; icon icon/accent |
| icon | icon/secondary, icon-size-20 |
| disabled row | text/disabled |
| nested rows | a border/subtle line on the start side |
| focus ring | border/accent/focus |
Values per theme:
Accessibility
Built in:
- The content is a
<nav>landmark named bylabel. The header and footer are outside it. - Rows are real links and buttons, each its own Tab stop, with no menu or tree roles.
currentsetsaria-current="page"; a row with nested rows reports whether it's open.- A collapsed panel is out of the Tab order and the accessibility tree. In the rail, rows stay reachable and keep their names.
- When the links overflow, the content becomes a named, focusable scroll region.
- The keyboard toggle ignores typing in fields.
Your part:
- Give every
<nav>on the page a differentlabel, such as "Main", "Sections", and "Breadcrumb". - Name the toggle button for its action.
- Use a Sheet for the mobile drawer.
| Key | Action |
|---|---|
Tab |
moves through the rows in order |
Enter |
follows a link row; opens or closes a row with nested rows |
Space |
opens or closes a row with nested rows |
Cmd / Ctrl + B |
opens or closes the sidebar |
There are no arrow keys: it's a column of links.