Skip to main content
cngx-src documentation

CngxSidenav

ComponentPrimaryOnPushNo encapsulationv0.1.0WCAG AA

projects/ui/sidenav/sidenav.ts

Import#

import { CngxSidenav } from '@cngx/ui/sidenav'

Description#

Declarative sidebar component for the four drawer modes (over, push, side, mini) plus an auto default, two-way [(opened)] binding, and content projection via header/footer slots.

Responsive out of the box: mode defaults to 'auto', which docks the rail beside the content once the surrounding CngxSidenavLayout is 64rem or wider and overlays it below that. Nothing needs to be bound. The threshold lives in sidenav-layout.css and nowhere else - a @container rule writes --cngx-sidenav-layout-wide on the rail and this component reads the resolved value, so ejecting the skin moves the breakpoint with it. Any explicit mode pins that mode at every width.

In overlay (over) mode it drives a CDK focus trap directly via FocusTrapFactory (the same primitive CngxFocusTrap wraps): focus moves into the rail on open and is restored to the opener on close. The shared backdrop and document scroll lock are coordinated by CngxSidenavLayout. It does not compose CngxDrawer and has no swipe-to-dismiss.

Use inside CngxSidenavLayout for dual-sidebar support and shared backdrop coordination.

<cngx-sidenav-layout>
  <cngx-sidenav position="start" [(opened)]="navOpen">
    <cngx-sidenav-header>Logo</cngx-sidenav-header>
    <a cngxNavLink [active]="true">Dashboard</a>
    <cngx-sidenav-footer>v1.0</cngx-sidenav-footer>
  </cngx-sidenav>
  <cngx-sidenav-content>
    <router-outlet />
  </cngx-sidenav-content>
</cngx-sidenav-layout>

https://cngxjs.github.io/cngx/examples/#/ui/sidenav/auto-mode https://cngxjs.github.io/cngx/examples/#/ui/sidenav/dual-sidebar-master-detail https://cngxjs.github.io/cngx/examples/#/ui/sidenav/full-navigation-sidebar https://cngxjs.github.io/cngx/examples/#/ui/sidenav/material-theming-light-vs-dark

Metadata#

Host#

Providers#

CNGX_HOVER_INTENT_DEFAULTS
useFactory () => injectSidenavConfig().hover ?? {}
CNGX_SIDENAV
useExisting CngxSidenav

Relationships

Index#

Inputs#

ariaLabel#string | undefined
input()

Accessible label for the complementary landmark.

expandOnHover#boolean
input()

Whether hovering the mini rail expands the sidenav to full width.

default true
maxWidth#string
input()

Maximum width constraint during resize. Use a px value: drag and keyboard clamping (and aria-valuemax) resolve px only; a rem/ch constraint falls back to the 600px default for clamping and omits the ARIA value.

default this.cfg.dimensions?.maxWidth ?? '600px'
miniWidth#string
input()

Width of the collapsed rail in mini mode. Any CSS value.

default this.cfg.dimensions?.miniWidth ?? '56px'
minWidth#string
input()

Minimum width constraint during resize. Use a px value: drag and keyboard clamping (and aria-valuemin) resolve px only; a rem/ch constraint falls back to the 120px default for clamping and omits the ARIA value.

default this.cfg.dimensions?.minWidth ?? '120px'
input()

Drawer mode. 'auto' (the default) resolves to 'side' once the surrounding layout is 64rem or wider and to 'over' below that; any other value pins that mode at every width.

default 'auto'
opened#boolean
model()

Two-way opened state. Supports [(opened)]="signal".

default false
input()

Logical position: 'start' (default left in LTR) or 'end' (right in LTR).

default 'start'
resizable#boolean
input()

Whether the sidebar is user-resizable via a drag handle.

default false
resizeLabel#string
input()

Accessible name of the resize separator. English default; per-instance like ariaLabel, since it names this rail's handle.

default 'Resize navigation'
shortcut#string | undefined
input()

Keyboard shortcut to toggle the sidenav, e.g. 'ctrl+b' or 'meta+b'. Uses ctrl on Windows/Linux and meta (Cmd) on macOS automatically when 'mod+<key>' syntax is used.

default this.cfg.shortcut
width#string
model()

Width of the sidenav panel. Supports two-way [(width)] for resize.

default this.cfg.dimensions?.width ?? '280px'

Outputs#

opened#boolean
model()

Two-way opened state. Supports [(opened)]="signal".

width#string
model()

Width of the sidenav panel. Supports two-way [(width)] for resize.

Instance Properties#

expanded#unknown
Readonly
this.expandedState.asReadonly()
resizing#unknown
Readonly
this.resizingState.asReadonly()

Methods#

close#void

Closes the sidenav (only in over/push mode). No-op in side/mini mode.

collapse#void

Collapse the expanded mini rail back to miniWidth.

Same reconciliation as expand(): transient while expandOnHover=true, authoritative while expandOnHover=false.

expand#void

Expand the mini rail to full width.

When expandOnHover=true (the default) the debounced hover is the source of truth for expanded, so a programmatic expand() is transient: the next pointer edge overrides it. For authoritative programmatic control set expandOnHover=false, which hands expand()/collapse() full authority.

open#void

Opens the sidenav.

toggle#void

Toggles the sidenav.

HostBindings#

BindingExpression
[class.cngx-sidenav]true
[class.cngx-sidenav--start]position() === 'start'
[class.cngx-sidenav--end]position() === 'end'
[class.cngx-sidenav--open]opened()
[class.cngx-sidenav--over]effectiveMode() === 'over'
[class.cngx-sidenav--push]effectiveMode() === 'push'
[class.cngx-sidenav--side]effectiveMode() === 'side'
[class.cngx-sidenav--mini]effectiveMode() === 'mini'
[class.cngx-sidenav--expanded]expanded()
[class.cngx-sidenav--resizable]resizable()
[class.cngx-sidenav--resizing]resizing()
[attr.aria-hidden]effectiveMode() === 'side' || effectiveMode() === 'mini' ? null : !opened()
[attr.aria-modal]overlayActive() ? 'true' : null
[style.--cngx-sidenav-width]effectiveWidth()
[style.--cngx-sidenav-mini-width]miniWidth()
[attr.aria-label]ariaLabel() || null

HostListeners#

EventHandler
(keydown.escape)keydown.escape()

Default structural and thematic styles for CngxSidenav - the rail itself (surface, borders, slots), the four mode variants, mini-mode, the resize handle, and the nav-link rules the rail applies to @cngx/common/interactive nav directives. Shipped via the component's styleUrl, so a bare <cngx-sidenav> is styled without a CngxSidenavLayout wrapper. Under encapsulation: None the cngx-sidenav tag and its descendants match globally; the cngx-sidenav- prefix is the namespace.

CngxSidenavLayout's own styles (flex container, content offset, backdrop scrim, the --ready transition gate, reduced-motion) live in sidenav-layout.css.

State modifiers

On cngx-sidenav:

  • --open - visible (otherwise width collapses to 0 in over mode, with visibility: hidden)
  • --start - left edge; border-right
  • --end - right edge; border-left, flex order: 2
  • --resizing - no transitions, user-select: none

On .cngx-nav-group-content:

  • --open - grid-rows 0fr -> 1fr expand for nav groups

Variants

Mode classes on cngx-sidenav:

  • --push - in flex flow, push content sideways
  • --side - in flex flow, stays visible (no width collapse)
  • --over - absolute-positioned, doesn't push content
  • --mini - icon-rail (--cngx-sidenav-mini-width); --expanded grows to full width with negative margin so the overlay does not shift the flex layout, plus --cngx-sidenav-expanded-shadow

Slots

  • .cngx-sidenav__body - scrollable rail content
  • .cngx-sidenav-header - sticky top slot, border-bottom
  • .cngx-sidenav-footer - sticky bottom slot, border-top, 70% opacity
  • .cngx-sidenav__resize-handle - col-resize drag bar on the rail's edge; tinted on hover / drag via --cngx-sidenav-resize-handle-color
  • .cngx-nav-link - nav-link rule with hover / --active / :focus-visible states; mini mode hides labels and renders first-letter initials via [data-initial]::before
  • .cngx-nav-group-content - grid-rows expand/collapse wrapper for nested nav groups

Mini-mode also hides [cngxNavLabel], [cngxNavGroup], and [cngxNavBadge].

Nav-class contract

The rail styles a set of class / attribute hooks emitted by the @cngx/common/interactive nav directives. These form a cross-lib contract: rename any hook in @cngx/common and mini-mode plus active-state styling break silently. sidenav-nav-contract.spec.ts mounts the real directives inside a cngx-sidenav and asserts the hooks still resolve, so a rename fails a test instead. The hooks:

  • .cngx-nav-link / .cngx-nav-link--active (CngxNavLink)
  • [data-initial] - first-letter initial shown in mini mode (CngxNavLink)
  • [cngxNavLabel] (CngxNavLabel), [cngxNavGroup] (CngxNavGroup), [cngxNavBadge] (CngxNavBadge), all hidden in collapsed mini mode
  • .cngx-nav-group-content / --open, the nested nav-group expand / collapse wrapper

Inheritance

  • --cngx-sidenav-bg -> --cngx-color-surface
  • --cngx-sidenav-border-color -> --cngx-color-border
  • --cngx-sidenav-footer-border-color -> --cngx-sidenav-border- color -> --cngx-color-border
  • --cngx-sidenav-resize-handle-color -> --cngx-color-primary
  • --cngx-sidenav-resize-handle-focus-outline-color -> --cngx-color-primary
  • --cngx-nav-link-focus-outline-color -> --cngx-color-primary

Dark mode

Three hooks deepen the expanded-rail shadow and flip the nav-link hover / active overlays from alpha-on-black to alpha-on-white:

  • prefers-color-scheme: dark
  • [data-color-scheme="dark"]
  • .dark class

Pair with

  • @cngx/themes/material/sidenav-theme - Material 3 rail surface
  • @cngx/themes/material/nav-link-theme - Material 3 nav-link palette

Index#

Layout

--cngx-sidenav-width#<length>
Default value 280px

Default open-width of the rail.

--cngx-sidenav-padding#<length>
Default value 16px

Padding shorthand inside the header / footer slots.

--cngx-sidenav-resize-handle-width#<length>
Default value 4px

Width of the drag-to-resize handle on the rail's edge.

Surface

--cngx-sidenav-bg#<color>
Default value oklch(0.97 0.005 250)

Background of the rail surface.

--cngx-sidenav-color#*
Default value currentColor

Text color of the rail. Defaults to currentColor so it inherits from the page.

--cngx-sidenav-border-width#<length>
Default value 1px

Stroke width of the rail edge border.

--cngx-sidenav-border-color#<color>
Default value oklch(0.89 0 0)

Stroke color of the rail edge border. Falls back through --cngx-color-border.

--cngx-sidenav-header-bg#<color>
Default value transparent

Background of the header slot.

Motion

--cngx-sidenav-transition-duration#<time>
Default value 0.25s

Duration of the open / close / mini transitions.

Derives from the --cngx-duration-base rung of the motion scale; a --cngx-duration-* brand override re-times it library-wide. @relatedTo provideMotion, CngxMotionScope, injectMotion

--cngx-sidenav-transition-easing#*
Default value ease

Easing curve of the open / close / mini transitions.

Variant / Mini

--cngx-sidenav-mini-width#<length>
Default value 56px

Collapsed width in mini mode (icon-rail).

--cngx-sidenav-expanded-shadow#*
Default value 4px 0 12px oklch(0 0 0 / 0.1)

Drop-shadow applied when the mini-rail expands over content.

Typography

--cngx-sidenav-header-font-size#*
Default value 0.875rem

Font-size of the header slot.

State / Resize

--cngx-sidenav-resize-handle-color#<color>
Default value oklch(0.66 0.19 50)

Color of the resize handle on hover / drag. Falls back to --cngx-color-primary.

See: [[--cngx-color-primary]]

--cngx-sidenav-resize-handle-focus-outline-width#<length>
Default value 2px

Width of the focus ring on the keyboard-focused resize separator.

--cngx-sidenav-resize-handle-focus-outline-color#<color>
Default value oklch(0.66 0.19 50)

Color of the focus ring on the keyboard-focused resize separator. Falls back to --cngx-color-primary, like the hover tint.

See: [[--cngx-color-primary]]

--cngx-sidenav-resize-handle-focus-outline-offset#<length>
Default value 2px

Outward offset of the resize separator's focus ring.

State / Over

--cngx-sidenav-backdrop-bg#<color>
Default value oklch(0 0 0)

Background of the mobile backdrop scrim.

--cngx-sidenav-backdrop-opacity#<number>
Default value 0.5

Opacity of the mobile backdrop scrim.

State / Hover

State / Active

State / Focus