c2-side-nav is an app-shell layout: the drawer goes in the side-nav-content slot, the page in the default slot, and both stretch to the host’s height. It has two display modes. In side mode the drawer is part of the layout: it collapses to --c2-side-nav__close--width (0 by default, or a rail) and pushes the content. In over mode it leaves the flow, slides over the content with a backdrop, locks page scrolling, behaves as a dialog for assistive technology and closes on Escape or a click outside. Phones (under 600px) always use over mode; tablet-mode (600 to 959px, over by default) and desktop-mode (960px and up, side by default) choose the mode for the larger breakpoints. When the viewport crosses a breakpoint the drawer closes or reopens by itself and remembers your choice; on phones the initial opened is ignored so the overlay never covers the page on load.
Give any element inside the side nav a side-nav-toggle attribute, in the page header or in the drawer itself, and clicking it toggles the drawer, no script needed; with nested side navs the toggle acts on the nearest enclosing one. opened reflects the state (so c2-side-nav:not([opened]) can restyle the collapsed drawer into an icon rail) and opened-change reports user-driven changes.
Installation
npm install @c2n/side-nav
Usage
The frames below are fixed-height containers; in an app the side nav usually fills the viewport (height: 100dvh). The docs viewport is a desktop, so resize the window to see the tablet and phone behaviour. Over mode is kept inside the frame here with --c2-side-nav__over--position: absolute; leave the default (fixed) in an app.
Inbox
The drawer is part of the layout and pushes this content. Click the menu button to collapse it; the content grows to fill the space.
AcmeInboxDraftsSentArchive
Inbox
The drawer is part of the layout and pushes this content. Click the menu button to collapse it; the content grows to fill the space.
AcmeInboxDraftsSentArchive
Inbox
The drawer slides over this content with a backdrop. Press Escape or click the backdrop to close it. This is what every mode becomes on a phone.
AcmeInboxDraftsSent
Inbox
The drawer slides over this content with a backdrop. Press Escape or click the backdrop to close it. This is what every mode becomes on a phone.
AcmeInboxDraftsSent
Messages
The drawer sits on the end edge; the divider and the slide direction follow.
FiltersAllUnreadFlagged
Messages
The drawer sits on the end edge; the divider and the slide direction follow.
FiltersAllUnreadFlagged
Icon rail
Collapsed to a 56px rail: the logo mark, the icons and their native title hints stay; the labels and the unread badge fade out. The collapse button sits in the drawer header. Click it: the drawer widens to 210px and the same rows show icon, label and badge, then click again to go back.
AAcme
Inbox12SentArchiveSettings
Icon rail
Collapsed to a 56px rail: the logo mark, the icons and their native title hints stay; the labels and the unread badge fade out. The collapse button sits in the drawer header. Click it: the drawer widens to 210px and the same rows show icon, label and badge, then click again to go back.
Toggle from markup with side-nav-toggle, from code with opened or toggle(), and listen to opened-change to keep your own controls in sync. The event fires for user actions and breakpoint changes, not for values you set yourself.
const nav = document.querySelector('c2-side-nav')nav.addEventListener('opened-change', (event) => { menuButton.setAttribute('aria-expanded', String(event.detail.opened))})nav.toggle() // flipnav.opened = false // set silentlyconsole.log(nav.mode) // 'side' | 'over' for the current viewport
An app shell is the host filling the viewport, with the page scrolling inside the default slot:
<c2-side-nav opened style="height: 100dvh"> <nav slot="side-nav-content">…</nav> <main style="overflow: auto">…</main></c2-side-nav>
Theming
--c2-side-nav--* styles the drawer panel (background, padding, borders, radius) and its widths; __divider--* is the line towards the content, __over--* overrides the panel and adds the shadow in over mode (each falls back to the base value), __backdrop--* the scrim. --c2-side-nav--transition-duration drives the collapse and slide. See the API tab for every variable and the Gallery tab for ready-made looks.
c2-side-nav is an app-shell layout: the drawer goes in the side-nav-content slot, the page in the default slot, and both stretch to the host’s height. It has two display modes. In side mode the drawer is part of the layout: it collapses to --c2-side-nav__close--width (0 by default, or a rail) and pushes the content. In over mode it leaves the flow, slides over the content with a backdrop, locks page scrolling, behaves as a dialog for assistive technology and closes on Escape or a click outside. Phones (under 600px) always use over mode; tablet-mode (600 to 959px, over by default) and desktop-mode (960px and up, side by default) choose the mode for the larger breakpoints. When the viewport crosses a breakpoint the drawer closes or reopens by itself and remembers your choice; on phones the initial opened is ignored so the overlay never covers the page on load.
Give any element inside the side nav a side-nav-toggle attribute, in the page header or in the drawer itself, and clicking it toggles the drawer, no script needed; with nested side navs the toggle acts on the nearest enclosing one. opened reflects the state (so c2-side-nav:not([opened]) can restyle the collapsed drawer into an icon rail) and opened-change reports user-driven changes.
Installation
npm install @c2n/side-nav
Usage
The frames below are fixed-height containers; in an app the side nav usually fills the viewport (height: 100dvh). The docs viewport is a desktop, so resize the window to see the tablet and phone behaviour. Over mode is kept inside the frame here with --c2-side-nav__over--position: absolute; leave the default (fixed) in an app.
Inbox
The drawer is part of the layout and pushes this content. Click the menu button to collapse it; the content grows to fill the space.
AcmeInboxDraftsSentArchive
Inbox
The drawer is part of the layout and pushes this content. Click the menu button to collapse it; the content grows to fill the space.
AcmeInboxDraftsSentArchive
Inbox
The drawer slides over this content with a backdrop. Press Escape or click the backdrop to close it. This is what every mode becomes on a phone.
AcmeInboxDraftsSent
Inbox
The drawer slides over this content with a backdrop. Press Escape or click the backdrop to close it. This is what every mode becomes on a phone.
AcmeInboxDraftsSent
Messages
The drawer sits on the end edge; the divider and the slide direction follow.
FiltersAllUnreadFlagged
Messages
The drawer sits on the end edge; the divider and the slide direction follow.
FiltersAllUnreadFlagged
Icon rail
Collapsed to a 56px rail: the logo mark, the icons and their native title hints stay; the labels and the unread badge fade out. The collapse button sits in the drawer header. Click it: the drawer widens to 210px and the same rows show icon, label and badge, then click again to go back.
AAcme
Inbox12SentArchiveSettings
Icon rail
Collapsed to a 56px rail: the logo mark, the icons and their native title hints stay; the labels and the unread badge fade out. The collapse button sits in the drawer header. Click it: the drawer widens to 210px and the same rows show icon, label and badge, then click again to go back.
Toggle from markup with side-nav-toggle, from code with opened or toggle(), and listen to opened-change to keep your own controls in sync. The event fires for user actions and breakpoint changes, not for values you set yourself.
const nav = document.querySelector('c2-side-nav')nav.addEventListener('opened-change', (event) => { menuButton.setAttribute('aria-expanded', String(event.detail.opened))})nav.toggle() // flipnav.opened = false // set silentlyconsole.log(nav.mode) // 'side' | 'over' for the current viewport
An app shell is the host filling the viewport, with the page scrolling inside the default slot:
<c2-side-nav opened style="height: 100dvh"> <nav slot="side-nav-content">…</nav> <main style="overflow: auto">…</main></c2-side-nav>
Theming
--c2-side-nav--* styles the drawer panel (background, padding, borders, radius) and its widths; __divider--* is the line towards the content, __over--* overrides the panel and adds the shadow in over mode (each falls back to the base value), __backdrop--* the scrim. --c2-side-nav--transition-duration drives the collapse and slide. See the API tab for every variable and the Gallery tab for ready-made looks.
escAccordionConnected, animated panels with shared borders and single or multiple expansion.LayoutAvatarImage, initials or icon for a person, with status dot and badge.Data displayBadgeTinted pill for status text, counts and dots, optionally pinned to a corner of another element.Data displayBreadcrumbNavigation trail of link buttons with separators, a current page and optional collapsing.NavigationButtonThemeable button with slots for text, prefix, suffix and running icons.ButtonsButton GroupAttached buttons with shared borders, optionally a segmented control with single or multiple selection.ButtonsCardGroups related content and actions on a single bordered surface.LayoutChat InputAuto-growing message box with a send button for chat interfaces.ChatChat MessageOne chat bubble with avatar, title, timestamp and message body.ChatCheckboxNative checkbox behaviour in a quiet, themeable box with an opt-in hover layer.InputsCode ViewerSyntax-highlighted code with line numbers, copy button and dark mode, powered by shiki.Data displayColor AreaTwo-dimensional area for picking saturation and value of a colour.InputsColor SelectColour swatch that opens a full picker built from area and slider.InputsColor SliderHorizontal slider for choosing a hue from 0 to 360.InputsCopy ButtonButton that copies text to the clipboard — the element it sits in, another element by id, or a literal string.ButtonsDetailsCollapsible disclosure built on native details and summary.LayoutIcon ButtonRound, hoverable button wrapping a slotted SVG icon.ButtonsKbdKeyboard key label for shortcuts and command hints, with the semantics of the native kbd element.Data displayLabelCaption that names and activates the control referenced by its for attribute, with a required marker.InputsLink ButtonText-styled control for link and navigation actions, rendered as a real anchor or a button.NavigationListVertical list container with single or multiple selection.Data displayList ItemSelectable row with icon slots, used on its own or as the option of list and select.Data displayMenuCommands, links, checkboxes and submenus in a popover anchored to a trigger.NavigationModalDialog built on the native dialog element: focus trap, backdrop, Escape, title, body and footer.FeedbackNavigation MenuSite navigation bar whose triggers open panels of links below the header.NavigationOverlayAnchored popup built on the browser Popover API, positioned with floating-ui.FeedbackPaginationPage navigation in three layouts: numbered pages, a simple page status, or a table-footer row with rows-per-page.NavigationProgressLinear progress bar, indeterminate or filling to a value, with an optional label and count.FeedbackRadioRadio options built on native inputs, grouped into one value with keyboard navigation.InputsSelectDropdown that pairs a themeable trigger with an anchored list of c2-list-item options.InputsSeperatorHorizontal or vertical rule with an optional label, for dividing content and toolbars.LayoutSheetDialog pinned to an edge of the screen, for content that complements the page rather than interrupting it.FeedbackSide NavResponsive navigation drawer beside the page: pushes the content on large screens, slides over it with a backdrop on small ones.NavigationSkeletonPlaceholder block standing in for content that has not arrived, in three shapes and three animations.FeedbackSliderRange input with a themeable track, thumb, step ticks and value bubble.InputsSpinnerCircular progress indicator, indeterminate or showing a value, with optional text.FeedbackSwitchOn/off toggle on a native switch input, with label, description and thumb icons.InputsTableVirtualized data grid with declarative columns, sorting, selection, pinning and resizing.Data displayTabsTab strip that shows one content panel at a time.NavigationText FieldSingle-line input with icon slots, clear button, helper and error text, and a character counter.InputsTextareaMultiline text input with resizing, helper and error text, and a character counter.InputsToastNotification cards and a manager for stacked, queued notifications with independent lifetimes.FeedbackTooltipContextual hint shown when its target is hovered or focused, rendered in the top layer.FeedbackFeather Icons287 open-source Feather icons, one web component each.IconsMat Icon2,234 Material Icons ligatures rendered through a single element.IconsPhosphor Icons1,512 flexible icons in six weights, one web component each.IconsThemingTheme every c2n component from a handful of design tokens with @c2n/theme, or reach for any component variable directly.GuideUsing c2n in an applicationThe workflow behind every c2n app: load the theme once, use c2-* tags directly, and turn every repeated pattern into a small variant or composed component.Guide