Navigation
Navigation Drawer
MD3 navigation drawer — the sidebar of a dashboard, in all three of the shapes a dashboard needs it in.
import { NavigationDrawer } from 'omaris' Examples
Standard
Sections group items and badge rides on the end of a row — the sidebar of a dashboard with more pages than one flat list can hold.
<script lang="ts"> import { NavigationDrawer, NavigationDrawerItem, NavigationDrawerSection } from 'omaris'; let selected = $state('inbox');</script><div class="h-80 w-72 max-w-full overflow-hidden rounded-shape-lg border border-border"> <NavigationDrawer label="Mail"> <NavigationDrawerSection label="Mail"> <NavigationDrawerItem label="Inbox" badge={12} selected={selected === 'inbox'} onclick={() => (selected = 'inbox')} /> <NavigationDrawerItem label="Sent" selected={selected === 'sent'} onclick={() => (selected = 'sent')} /> </NavigationDrawerSection> <NavigationDrawerSection label="Commerce" divider> <NavigationDrawerItem label="Orders" badge="New"> {#snippet submenu()} <NavigationDrawerItem label="Open" badge={4} /> <NavigationDrawerItem label="Shipped" /> {/snippet} </NavigationDrawerItem> </NavigationDrawerSection> </NavigationDrawer></div> Collapsed rail
Collapsed, it becomes MD3's rail: icons only, labels as tooltips. What a laptop wants once the content needs the width more than the labels.
<script lang="ts"> import { Button, NavigationDrawer, NavigationDrawerItem } from 'omaris'; let collapsed = $state(true);</script>{#snippet overview()} ...{/snippet}{#snippet orders()} ...{/snippet}{#snippet team()} ...{/snippet}<div class="flex w-full flex-col items-start gap-3"> <Button size="xs" variant="outlined" onclick={() => (collapsed = !collapsed)}> {collapsed ? 'Expand' : 'Collapse'} </Button> <div class="h-64 overflow-hidden rounded-shape-lg border border-border"> <NavigationDrawer bind:collapsed label="Sections"> <NavigationDrawerItem label="Overview" selected> {#snippet icon()}{@render overview()}{/snippet} </NavigationDrawerItem> <NavigationDrawerItem label="Orders"> {#snippet icon()}{@render orders()}{/snippet} </NavigationDrawerItem> <NavigationDrawerItem label="Team"> {#snippet icon()}{@render team()}{/snippet} </NavigationDrawerItem> </NavigationDrawer> </div></div> Modal
mode="modal" slides it over the page on a scrim. In an app, responsive is the one to reach for: a sidebar above the breakpoint, this modal below it, so you write one drawer instead of two.
<script lang="ts"> import { Button, NavigationDrawer, NavigationDrawerItem } from 'omaris'; let open = $state(false);</script><Button variant="tonal" onclick={() => (open = true)}>Open the drawer</Button><NavigationDrawer bind:open mode="modal" label="Sections"> {#snippet header()} <span class="px-2 py-1 text-title-md">omaris</span> {/snippet} <NavigationDrawerItem label="Overview" selected /> <NavigationDrawerItem label="Orders" badge={12} /> <NavigationDrawerItem label="Settings" /></NavigationDrawer> When to use it
Use it for
- The sidebar of a dashboard, inside an
AppShellSidebar: sections, nested groups viasubmenu, a logo inheader, the account row infooter. - The rail.
collapsednarrows it to icons with labels as tooltips. There is no NavigationRail component; this is it. It is bindable, so a button in the app bar can toggle it. - A phone or tablet.
mode="modal"slides it over the page on a scrim;responsivepicks between the two atbreakpoint, so one drawer serves every screen. - More destinations than a bar can hold, or destinations that nest.
Not for
- Three to five destinations on a phone → Navigation Bar.
- Views within one page → Tabs. The drawer maps the app, not a page.
- A filter panel, an inspector or a form sliding in from an edge → Sheet.
- A list of actions off a button → Menu.
- Where the current page sits in a hierarchy → Breadcrumb.
Do
- Pair
responsivewithbreakpoint={false}on theAppShellSidebar, or the shell drops the column before the drawer can go modal. - Add
borderedwhen the drawer sits on the same colour as the page. It draws no border on its own. - Give every item an
icon. Collapsed, a row without one is an empty pill. - When modal, bind
opento a menu button in theleadingslot of the Top App Bar and leavecloseOnNavigateon. - Put "Sign out" and the account in
footer, not among the destinations.
Don't
- Leave a phone with nothing.
AppShellSidebarhides belowmd; withoutresponsivehere or a Navigation Bar, the phone has no navigation. - Nest deeper than one
submenu. A second level means the section wants a page of its own with Tabs. - Turn on
resizablefor the look. It is for labels that do not fit at280. SetminWidthso the rail cannot be dragged into. - Rebuild the modal drawer from a Sheet.
mode="modal"already has the scrim, the drag and close-on-navigate.
API
NavigationDrawer
MD3 navigation drawer — the sidebar of a dashboard, in all three of the shapes a dashboard needs it in.
- standard is the always-there sidebar of a desktop layout. - collapsed narrows it to its icons, which is what most dashboards actually want on a laptop. Every row keeps its label as a tooltip, so nothing is lost by narrowing it. - modal is the temporary drawer of a phone or a tablet: it slides over the page on a scrim, closes on Escape, on a press outside, on choosing a destination — and follows a finger dragged toward the edge, springing back if the drag was half-hearted.
responsive picks between the last two on the viewport, so one drawer is right on every screen without the layout knowing which it got.
resizable lets the sidebar be dragged to a width that suits the content — the edge is a real separator, so the arrow keys move it too.
import { NavigationDrawer } from 'omaris' Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
body- Scrolls; the header and footer don't.
header- No description in the source yet.
footer- No description in the source yet.
dialog- The modal shell: a dialog on a scrim, sliding in from the edge.
grip- The drag target on the trailing edge of a resizable drawer.
Props
collapsed bindableDefaults to false
boolean Narrow the drawer to its icons. Bindable.
bordered Defaults to false
boolean label string Accessible name.
mode Defaults to 'standard'
NavigationDrawerMode Beside the content, or over it. responsive overrides this.
open bindableDefaults to false
boolean Whether a modal drawer is showing. Bindable.
responsive Defaults to false
boolean Be a modal drawer on a narrow viewport and a standard one on a wide one. true switches at breakpoint.
breakpoint Defaults to 900
number Viewport width, in px, at or above which the drawer stays standard.
closeOnNavigate Defaults to true
boolean Close a modal drawer when a destination inside it is chosen.
resizable Defaults to false
boolean Let the trailing edge be dragged to set the width.
width bindableDefaults to 280
number Width in px while expanded. Bindable; only used when resizable.
minWidth Defaults to 220
number maxWidth Defaults to 420
number onclose () => void class string classes NavigationDrawerClasses Per-part Tailwind overrides. class still covers the root.
header Snippet Pinned to the top — a logo, a compose button.
footer Snippet Pinned to the bottom — the account row, usually.
children Snippet NavigationDrawerItem
A destination in a NavigationDrawer.
MD3's drawer item is a 56dp pill: the whole row is the active indicator, unlike the bar item where only the icon gets one. When the drawer collapses, the row becomes a centred icon — the label survives both as its accessible name and as a tooltip, so a narrowed sidebar is still readable rather than a column of guesses.
Give it a submenu and it becomes a group: the row grows a chevron and the nested destinations open under it against a guide line, which is how a dashboard's navigation actually nests.
The row collapses; it is not rebuilt. Every element here is in the DOM in both states — the label and the trailing content included — and the narrowing is a transition on the properties that differ. Nothing is added, removed or reparented on the way, which is what lets the row travel with the sidebar's own width instead of snapping to the new shape on the first frame and leaving the panel to catch up.
import { NavigationDrawerItem } from 'omaris' Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
tail- Everything after the icon, collapsed as one piece.
label- No description in the source yet.
trailing- Trailing count or badge.
chevron- The chevron on a group row.
submenu- Animates the nested rows open without anyone measuring them.
nest- The guide line the nested rows hang off.
Props
label string Row text — and the accessible name once the drawer collapses.
selected Defaults to false
boolean badge number | string Trailing count.
ripple Defaults to true
boolean expanded bindableDefaults to false
boolean Whether the nested rows are showing. Bindable.
class string classes NavigationDrawerItemClasses Per-part Tailwind overrides. class still covers the root.
icon Snippet selectedIcon Snippet Icon shown only while selected.
trailing Snippet Trailing slot, in place of badge.
submenu Snippet Nested destinations. Their presence turns the row into a group.
children Snippet variant ButtonVariant Fill style. MD3's five, plus link for inline navigation.
tone ButtonTone Color role the fill style paints with.
size ButtonSize MD3 Expressive heights: 32 / 40 / 56 / 96 / 136dp.
shape ButtonShape Pill (round) or MD3 rounded rectangle (square).
confetti boolean | ConfettiOptions Confetti on press. true for the default burst, or any ConfettiOptions — confetti={{ preset: 'fireworks' }}.
loading boolean Grows a spinner in at the start of the button and blocks interaction. The label stays put; only an icon button swaps its icon out.
toggle boolean Turns the button into a two-state toggle driven by pressed. Ignored when href is set — a link has no pressed state.
pressed bindableboolean Selected state of a toggle button. Bindable.
NavigationDrawerSection
A labelled group of items inside a NavigationDrawer.
The label folds away when the drawer is collapsed to a rail, so a rail does not show headings with nothing under them — folds rather than disappears, on the drawer's own curve and duration, and it stays in the accessibility tree the whole time so the group keeps its name.
import { NavigationDrawerSection } from 'omaris' Props
label string Group heading. Hidden (but still announced) when collapsed.
divider Defaults to false
boolean Hairline above the group.
class string children Snippet