Navigation Drawer
A navigation drawer is the top level navigation for wide screens, with room for labels, groups and
counts. It comes in two shapes. Without modal it is a standard drawer: part of the
layout, always visible next to the content. With modal it becomes a popover that slides
in over the page and is dismissed again, which is what you want on a narrow screen.
The examples below show both. The first is modal and opens from a button, the second is a standard drawer with its height capped so it fits on this page.
Usage
<Button popovertarget="demo-drawer">Open Nav</Button>
<NavigationDrawer id="demo-drawer" modal backdrop>
<NavigationDrawerItem
selected={selection === 1}
onclick={() => {
selection = 1
}}
label="Videos"
badgeLabelText="+100"
>
{#snippet icon()}<Icon>videocam</Icon>{/snippet}
</NavigationDrawerItem>
<NavigationDrawerItem
selected={selection === 2}
onclick={() => {
selection = 2
}}
label="Styles"
>
{#snippet icon()}<Icon>palette</Icon>{/snippet}
</NavigationDrawerItem>
<NavigationDrawerItem
selected={selection === 3}
onclick={() => {
selection = 3
}}
label="Settings"
>
{#snippet icon()}<Icon>settings</Icon>{/snippet}
</NavigationDrawerItem>
</NavigationDrawer><NavigationDrawer --np-navigation-drawer-height="200px">
<NavigationDrawerItem
selected={selection === 1}
onclick={() => {
selection = 1
}}
label="Videos"
badgeLabelText="+100"
>
{#snippet icon()}<Icon>videocam</Icon>{/snippet}
</NavigationDrawerItem>
<NavigationDrawerItem
selected={selection === 2}
onclick={() => {
selection = 2
}}
label="Styles"
>
{#snippet icon()}<Icon>palette</Icon>{/snippet}
</NavigationDrawerItem>
<NavigationDrawerItem
selected={selection === 3}
onclick={() => {
selection = 3
}}
label="Settings"
>
{#snippet icon()}<Icon>settings</Icon>{/snippet}
</NavigationDrawerItem>
</NavigationDrawer>Opening a modal drawer
A modal drawer is a native popover. The simplest way to open it is popovertarget on the
trigger, as in the example above. When you need to open or close it from code, bind the element and
call the popover methods on it.
<script lang="ts">
let drawer = $state<HTMLElement>()
</script>
<Button onclick={() => drawer?.showPopover()}>Menu</Button>
<NavigationDrawer bind:element={drawer} modal backdrop>
<NavigationDrawerItem label="Videos" onclick={() => drawer?.hidePopover()} />
</NavigationDrawer>Pass backdrop to dim the page behind the drawer and let a click outside close it. Set direction="rtl" to have the drawer slide in from the other edge, which is what you want
when it sits at the end of the layout or in a right-to-left context.
Accessibility
The drawer renders a <nav>, and the selected item gets aria-current="page". Give the <nav> an aria-label when the page has more than one navigation landmark.
The items share a single tab stop. Tab moves into and out of the drawer as a whole, ↑ and ↓ move between the destinations and wrap around at the ends, and Home and End jump to the first and the last one.
While a modal drawer is open the rest of the page is marked inert, so neither the
keyboard nor the screen reader cursor can leave it. Focus moves to the first item on open and back
to the trigger on close, and Escape closes the drawer.
Theming
| Token | Default value |
|---|---|
--np-navigation-drawer-background | --np-color-surface-container-low |
--np-navigation-drawer-width | 22.5rem |
--np-navigation-drawer-height | 100dvh |
--np-navigation-drawer-padding | 1.25rem 0.75rem |
--np-navigation-drawer-item-container-shape | --np-shape-corner-full |
--np-navigation-drawer-item-font-size | 0.875rem |
--np-navigation-drawer-item-font-weight | 500 |
--np-navigation-drawer-item-selected-font-weight | 500 |
--np-navigation-drawer-height is the one to reach for when the drawer does not own
the full viewport, as in the standard example above. The selected item is drawn with secondary-container and on-secondary-container, the rest with on-surface-variant.
Example
<NavigationDrawer
--np-navigation-drawer-width="16rem"
--np-navigation-drawer-background="var(--np-color-surface-container-highest)"
--np-navigation-drawer-item-container-shape="0.5rem"
>
<NavigationDrawerItem selected label="Videos">
{#snippet icon()}<Icon>videocam</Icon>{/snippet}
</NavigationDrawerItem>
</NavigationDrawer>API
NavigationDrawer attributes
Everything else you pass is forwarded to the <nav> element, so id, aria-label, class, style and ontoggle work as expected.
| Attribute | Type | Default | Description |
|---|---|---|---|
modal | boolean | false | Turns the drawer into a popover that slides in over the page and traps focus. |
backdrop | boolean | false | Dims the page behind a modal drawer. Clicking the scrim closes it. |
direction | 'ltr' | 'rtl' | 'ltr' | Edge the drawer slides in from. |
element | HTMLElement | undefined | undefined | Bindable reference to the <nav>. Use it to call showPopover() and hidePopover(). |
NavigationDrawerItem attributes
Everything else you pass is forwarded to the underlying <button> or <a>, including onclick and href.
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | Required. Text of the destination. | |
icon | Snippet | undefined | undefined | Leading icon. It is filled while the item is selected. |
selected | boolean | undefined | undefined | Marks the current destination and sets aria-current="page". |
badgeLabelText | string | undefined | undefined | Trailing text, for a count such as "+100". |
href | string | undefined | undefined | Renders the item as a link instead of a button. |
type | 'submit' | 'reset' | 'button' | null | undefined | Button type, for the cases where the item lives inside a form. |