Sidebar
An app navigation panel — collapsible to an icon rail on desktop, a native-dialog drawer below the mobile breakpoint — with no rail and no nested submenus.
Sidebar is an app navigation panel: collapsible to icons on desktop, a native <dialog> drawer
(Sheet) below the mobile breakpoint. It covers the common case — no rail, no nested
submenus, no skeleton loading — with a real focusable trigger, a genuinely modal mobile drawer, and item labels
that stay in the accessibility tree once the panel goes icon-only.
Minimal example
How it works
Sidebar renders as a native <aside> on desktop, and swaps to a Sheet anchored to
side() below the 768px breakpoint (via BreakpointObserverService)
— projecting the exact same sidebarHeader/sidebarContent/sidebarFooter content either way, nothing to
branch on in application code.
The mobile drawer starts closed, and its entire content — including a trigger placed inside sidebarHeader —
lives inside it. Below the breakpoint there is nothing on screen to open it from unless the app keeps a
second trigger outside the <Sidebar> (its own top bar, typically), passed the panel explicitly since it
cannot resolve SIDEBAR_REF through DI from outside:
The mobile drawer reuses Sheet unmodified — focus trap, Escape-to-close, focus restoration and the
::backdrop all come from the native <dialog> + showModal() — sized to the full viewport width (a fixed
Sheet width can otherwise push the in-header trigger off-screen on a narrow phone) and painted with the same
--sidebar/--sidebar-foreground tokens as the desktop panel, so a themed app gets a matching drawer with no
extra work.
Recipes
Trailing badge and disabled item
A trailing Badge is pinned to the end of an item and dropped automatically once the
sidebar is icon-only — there is no room next to a bare icon for a pill.
<a sidebarItem routerLink="/notifications">
<BellIcon />
<span sidebarItemLabel>Notifications</span>
<badge size="xs">3</badge>
</a>
<a sidebarItem href="#" disabled>
<CogIcon />
<span sidebarItemLabel>Locked setting</span>
</a>disabled reflects aria-disabled="true" and sets tabindex="-1" — the same contract as
Link's own disabled.
inset and floating variants
variant="inset" draws no border or fill on Sidebar itself; the encased look comes from sidebarInset on
the app's main content wrapper instead, which supplies the surface. variant="floating" needs no companion
element — it detaches with a margin, border and shadow of its own, and reserves no layout space, ever,
floating on top of whatever sibling content follows it.
floating/inset collapse differently from the default sidebar variant: instead of narrowing to an icon
rail, the whole panel slides off-screen (and is marked inert while hidden, so its own header trigger
cannot still be reached by keyboard). An app using either variant with collapse therefore needs a third
trigger placed outside the <Sidebar>, shown only while collapsed() is true.
Reading the collapsed state from a projected component
A component projected inside sidebarHeader/sidebarContent/sidebarFooter reads the panel's own state by
injecting SIDEBAR_REF — the same token SidebarTriggerDirective itself resolves through.
Options
Prop
Type
SidebarTriggerDirective
Prop
Type
SidebarTriggerDirective also reflects aria-expanded on its host, bound to the resolved sidebar's
expanded() signal — a screen reader announces whether activating it opens or closes the panel, whichever
viewport applies.
SIDEBAR_REF
An InjectionToken<SidebarRef> a <Sidebar> provides for anything projected into it, exposing collapsed,
isMobile, expanded (all Signal<boolean>) and toggle()/open()/close() — the last three branch on
isMobile() automatically (desktop collapse vs. the mobile drawer).