Pricing

Sidebar handbook

v1.9.5 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

A collapsible, independently scrolling sidebar for brand links, grouped navigation, and bottom actions in multi-page applications.

When to use it

Use it for left-side grouped navigation with SidebarSection and SidebarItem. The page owns routing and content. Public logo and footer roles replace the defaults; a general right-side information panel is outside Sidebar’s scope.

  • sections supplies navigation data. Explicit children render alongside sections rather than replacing them.

  • A direct child with as="logo" replaces the logo; as="footer" goes to the fixed bottom area. width and collapsed can be bound. Persistence is enabled by default.

  • Default navigation and scroll-region names and built-in collapse controls, including the floating expand button in text mode, follow LocaleProvider. An explicit ariaLabel takes priority over Provider messages.

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import { Avatar, Button, Icon, Separator } from '@snowui-design-system/components';	import { AvatarByewind, Github } from '@snowui-design-system/resource-svelte';	import { Sidebar, SidebarItem, SidebarSection } from '@snowui-design-system/components';	let collapsed = $state(false);	let activeItemId = $state('components');</script> <div style="display: flex; align-items: flex-start; justify-content: center; width: min(100%, 24rem); height: 24rem; min-width: 0;">    <Sidebar bind:collapsed activeItemId={activeItemId} onActiveItemChange={(id: string) => activeItemId = id} persistence={false} resizable={false} ariaLabel="SnowUI documentation" navigationVariant="icon">        <SidebarItem id="introduction" label="Introduction" icon="book" iconCollection="phosphor" />        <SidebarItem id="quick-start" label="Quick Start" icon="rocket" iconCollection="phosphor" updated />        <Separator />        <SidebarSection id="library" title="Library">            <SidebarItem id="components" label="Components" icon="squares-four" iconCollection="phosphor" />            <SidebarItem id="charts" label="Charts" icon="stack" iconCollection="phosphor" />        </SidebarSection>        <div data-as="footer" style="display: flex; align-items: center; gap: var(--gap-3);">            <Button bare iconOnly href="https://byewind.com" target="_blank" rel="noopener noreferrer" aria-label="SnowUI maintainer"><Avatar src={AvatarByewind} size={24} dataName="SnowUI maintainer" /></Button>            <Button bare iconOnly href="https://github.com/SnowUI" target="_blank" rel="noopener noreferrer" aria-label="SnowUI on GitHub"><Icon size={24}><Github /></Icon></Button>        </div>    </Sidebar></div>

API

Keyword Type Default Description
sections SidebarSectionData[] [] Section array. Each section needs a unique id and may include title, collapsible, defaultCollapsed, actions, items, or content. content takes precedence over items; items use SidebarItemData fields such as id, label, icon, and href.
navigationVariant "icon" | "text" "icon" Navigation layout. text removes icon navigation; when collapsed, root width and padding become zero and only an expand button remains at the top-left of the visible viewport. Content is not scanned to infer icons.
collapsed boolean — Controlled collapse state; supports bind:collapsed. If omitted, initialize internal state from defaultCollapsed.
defaultCollapsed boolean false Initial uncontrolled collapse state.
width number 220 Expanded width; supports bind:width and updates live while the right edge is dragged.
minWidth number 160 Minimum draggable expanded width.
maxWidth number 480 Maximum draggable expanded width.
resizable boolean true Whether to enable the draggable right-edge hit area.
activeItemId string — Externally selected item ID. When omitted, use internal or restored selection. This controls visuals only; the host handles routing.
persistence boolean true Whether to persist the active item and each section’s collapse state.
persistenceKey string "default" Instance namespace in localStorage. Give each Sidebar on the same origin its own key.
ariaLabel string Locale message Accessible name for navigation and its scroll region. When omitted, follows LocaleProvider; an explicit value takes priority. Give multiple instances distinct names.
continuousHover boolean true Move a shared hover background between enabled navigation items. false restores individual hover backgrounds; selection is unaffected.
onCollapsedChange (collapsed: boolean) => void — Called with the new collapse state when the built-in button is clicked. The callback cannot cancel the change.
onWidthChange (width: number) => void — Called after user resizing with the integer pixel width clamped by minWidth and maxWidth.
onActiveItemChange (itemId: string) => void — Called with the selected item ID on navigation selection or restoration of a valid persisted selection. The host handles content and routing.
children Snippet — Parameterless explicit content rendered alongside sections. Direct as="logo" replaces the Header logo, as="footer" fills the fixed Footer, and other content enters the navigation body.
parts ComponentPartsOf<{ Indicator: typeof Indicator; SnowUILogo: typeof SnowUILogo; SidebarSection: typeof SidebarSection; SidebarItem: typeof SidebarItem; }> — Configure the resize Indicator and generated SnowUILogo, SidebarSection, and SidebarItem. Indicator keeps its default resize appearance.

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

API reference

The default logo host has as="logo", with SnowUILogo as="logo-icon" variant="icon" and SnowUILogo as="logo-wordmark" variant="text" separating icon and wordmark. A direct as="logo" child replaces the default Header logo. A custom logo must mark its persistent folded icon with a direct as="logo-icon" child; other direct children are treated as expanded text or wordmark and hidden when folded. A direct as="footer" child fills the bottom Footer. These roles do not suppress Body content generated by sections; other explicit children render in Body alongside sections. Wrap third-party content that lacks as with a Frame bearing the role. The root exposes data-collapsed and Sidebar Context.

<Sidebar activeItemId="dashboard" persistenceKey="workspace-primary">  <Frame as="logo" singleLine>    <BrandIcon as="logo-icon" />    <Text lines={1}>Brand</Text>  </Frame>  <SidebarItem id="dashboard" label="Dashboard" icon="dashboard" updated />  <Separator />  <SidebarSection id="page" title="Page">    <SidebarItem id="social" label="Social media" icon="chat" />  </SidebarSection>  <Text as="footer">© 2026 SnowUI</Text></Sidebar>

Layout and behavior

  • Expanded width defaults to 220px; collapsed width is 60px for icon navigation or 0px for text navigation; height is 100%. With resizable={true}, the full right edge drags the expanded width within the default 160–480px range, updating width/bind:width and onWidthChange; Indicator marks the edge. With resizable={false}, the edge hit area is absent.

  • Collapse animates the root width to 60px (icon) or 0px (text) with SIDE_PANEL_DISAPPEAR_MOTION; expand restores the last width with SIDE_PANEL_APPEAR_MOTION. Both reuse Frame’s critically damped move spring. Dragging disables width transitions so the edge follows the pointer. enableDefaultMotion={false} or reduced-motion preferences remove the transition.

  • The draggable right-edge hit area exists only while expanded with resizable={true}. It spans the full height and displays Indicator’s default short bar. Where it overlaps the visible overlay scrollbar thumb, Indicator’s resize priority applies; uncovered scrollbar area still responds. Folding preserves the adjusted expanded width for restoration.

  • Root padding defaults to 12px horizontally (var(--padding-3)) and 16px vertically (var(--padding-4)). Callers need not add those values through class or style.

  • Header and Footer do not scroll. Footer stays at the bottom; ScrollArea scrolls independently to the bottom and fades beneath Footer. The Header-to-Body gap is var(--gap-3) (12px), separate from the 4px gap between adjacent Body navigation entries.

  • The Body overlay scrollbar shifts outward according to root horizontal padding; its track stays 4px from the Sidebar’s right border. With the default 12px padding, its CSS right offset is -8px.

  • The logo Header has 4px vertical padding. The scroll viewport extends behind it and fades upward across the fixed area of Header height, top padding, and Header-to-Body gap: content remains 20% visible at 20% into that area and disappears by 40%. The logo itself is unmasked, and the list reserves space at its top.

  • Body navigation content uses var(--gap-1) (4px) spacing by default, and the directly combined SidebarItem is consistent with the internal list of SidebarSection.

  • SidebarItemData.depth={1} and explicit SidebarItem depth={1} share the same second-level layout. A 1px Black/40% dense dotted L-shaped connector with a 4px rounded corner replaces the ordinary leading icon. Its vertical center aligns with a first-level icon; its horizontal arm aligns with second-level text.

  • Every horizontal Separator in Sidebar uses 8px horizontal and vertical padding (var(--padding-2)), line-height: 0, and Separator’s default 0.5px Black/10% line. Content-box background clipping expresses this padding without a wrapper. Override it with --snowui-sidebar-separator-padding-x-override or --snowui-sidebar-separator-padding-y-override. Each SidebarSection includes its closing separator by default.

  • Persistence is enabled by default. Without persistenceKey, snowui:sidebar:default saves the active item and each Section’s collapse state. On refresh, the last active item and section states return. If the active item is in a collapsed Section, Sidebar expands that Section and saves the updated state. External activeItemId overrides stored selection. An ungrouped active item must not inherit a stale Section ID. Use a stable unique persistenceKey for multiple Sidebars on one origin, or persistence={false} to disable storage.

  • After restoring an active item, scroll only if Body overflows vertically. Center the item in the actual visible Body area, clamped to the scroll bounds. Position once immediately after restore, then correct after the default Section layout effect to account for changed collapse geometry.

  • The active visual state is managed by Sidebar; the host still holds the actual navigation and content switching through activeItemId / onActiveItemChange. When restoring the legal persistent active item, Sidebar will call onActiveItemChange to notify the host.

  • The default and custom as="logo" host both use 4px left and right padding to align the Logo graphic vertically with the SidebarItem icon.

  • Header preserves one alignment structure across expanded and collapsed states, with the icon in a stable leading slot. The example uses a fixed-width, left-aligned host so changing root width does not shift the whole mark. The logo host stays on one line; clipping and opacity hide the wordmark at narrow widths rather than wrapping or abruptly changing alignment.

  • The built-in collapse control is <Button iconOnly size="sm"> on the Logo’s Header row, to the right of its host. Header flex positions its transparent --size-6 hit box. Expanded mode adds var(--padding-1) (4px) right margin, shifting the button 4px left; collapsed mode omits it. A ResourceSize 16 icon stays centered. At a nominal 16px root font, the expanded icon remains 8px inside the shared Header/navigation content right edge; do not alter this with extra transforms or non-token offsets. The logo host takes remaining Header width with a stable 36px minimum leading slot. Frame’s move spring carries the button continuously from the logo-area right edge to its folded position. Its transparent hit box also reveals it when the pointer enters the expected position, including beyond the collapsed root’s visible bounds. In navigationVariant="icon", hover and focus-within control visibility. In collapsed navigationVariant="text", Logo, Body, Footer, root width, padding, and gap disappear; the expand button moves out of root layout into the body and browser top layer when available. Its fixed hit box sits var(--padding-3) (12px nominally) from the viewport left and var(--padding-4) (16px) from the top, unaffected by page scroll; older browsers use a body overlay. The button retains its icon and size. The Header-to-overlay handoff animates continuously from the current visible position, using the same spring and allowing rapid reversals. Reduced motion or enableDefaultMotion={false} positions it immediately. Keyboard focus transfers between the built-in and floating controls; the hidden Sidebar becomes inert and aria-hidden.

  • On collapse, root width, Item text, and Section title change together. The leading Item icon stays horizontally fixed while text and title fade using Frame’s color bridge. Once the title is transparent, its height, padding, and Section gap shrink with the Frame size bridge and Items move upward. Expansion reverses the same bridges.

  • The collapse button has no built-in Tooltip; its changing aria-label names the “expand sidebar” or “collapse sidebar” action. Other built-in Tooltips for collapsed Items, Item actions, and Section actions appear to the right (position="right"). Collapsible Section titles have no Tooltip.

  • The background color is not set by default.

  • Footer provides a fixed bottom region without business copy. It aligns content at the top-left, lays children out horizontally with vertical centering and 8px gap. Explicit singleLine footer content retains at least 4 CSS px of horizontal spacing and larger existing overrides; ordinary footer spacing is unchanged. It spans the full bottom width, and has 12px top, 16px bottom, and 20px horizontal padding. It has no radius, background color, or backdrop blur, preserving the host border and avoiding blur repaint during resize. A direct as="footer" child supplies content. During collapse and expansion, Footer keeps its expanded content width and bottom position; only opacity follows the Sidebar width timeline. When collapsed, inert, aria-hidden, and pointer-events: none remove interaction.

  • With Footer present, Sidebar tracks its actual height through shared resizeManager. Scrolling content extends to the bottom and uses an alpha mask beneath Footer: visibility is 100% at its top, 20% at 20% of its height downward, and zero by 40%. Footer content is not masked; no theme-specific background overlay is needed. A safe tail region equal to Footer height plus 12px keeps the last navigation item fully above Footer at maximum scroll. Declarative styles maintain its height during resize. The logo area mirrors the fade curve vertically.


Continuous hover

continuousHover defaults to true. A single navigation background moves between enabled SidebarItems, including those in Sections. The active item retains its persistent background, so hovering it stacks the two colors. Section titles interrupt the shared hover region: over a title, the moving layer hides rather than treating it as an adjacent Item gap. Set false for individual item hover backgrounds.

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

Variable Purpose and default reference
--snowui-separator-thickness-override Separator thickness.
--snowui-sidebar-collapsed-header-gap-override Folding state Logo host and folding button spacing (default var(--gap-1))
--snowui-sidebar-collapsed-logo-slot-width-override Icon variant folding state Logo leading slot width (default 36px)
--snowui-sidebar-collapsed-width-override Folding width (default 60px)
--snowui-sidebar-default-logo-gap-override Default Logo icon / wordmark spacing (default calc(var(--gap-1) * 1.5))
--snowui-sidebar-footer-gap-override Footer content spacing (default var(--gap-2) = 8px)
--snowui-sidebar-footer-padding-x-override Overlay Footer horizontal padding (default var(--padding-5) = 20px)
--snowui-sidebar-footer-padding-y-override Overwrite Footer vertical padding (default upper 12px, lower 16px)
--snowui-sidebar-gap-override Root-level Header / Body spacing (default var(--gap-3) = 12px)
--snowui-sidebar-item-background-override Hover / active background (default var(--black-4))
--snowui-sidebar-item-radius-override Navigation item rounded corner (default var(--radius-3))
--snowui-sidebar-logo-padding-x-override Default/custom Logo host horizontal padding (default var(--padding-1))
--snowui-sidebar-logo-padding-y-override Vertical padding in the Header area where Logo is located (default var(--padding-1) = 4px)
--snowui-sidebar-logo-scroll-mask-size-override Logo area reverse fading height (default Header actual height + top padding + Header/Body gap)
--snowui-sidebar-navigation-gap-override Body adjacent navigation content spacing (default var(--gap-1))
--snowui-sidebar-padding-x-override Root horizontal padding (default var(--padding-3) = 12px; Body scrolling bar keeps the distance between the right border of the Sidebar 4px)
--snowui-sidebar-padding-y-override Root vertical padding (default var(--padding-4) = 16px)
--snowui-sidebar-resize-indicator-offset-override Width on the right side of the expanded state Indicator Visual center from the right edge (default var(--padding-) = 4px)
--snowui-sidebar-separator-padding-x-override Horizontal padding of all horizontal Separator in Sidebar (default var(--padding-2) = 8px)
--snowui-sidebar-separator-padding-y-override Vertical padding of all horizontal Separator in Sidebar (default var(--padding-2) = 8px)
--snowui-sidebar-width-override Expanded width (default 220px)

Design Guide · Token · Combination boundary

Required Props: no named required Props.

Two-way binding: bind:collapsed, bind:width.

Inheritance statement: Omit<FrameProps, "children" | "width">. The inheritance ability is subject to the public document of the base class; the fields in Omit have been excluded and cannot be used as they are according to the base class.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

SidebarSectionData

Source: components/src/blocks/Sidebar.svelte

export interface SidebarSectionData {        id: string;        title?: string;        collapsible?: boolean;        defaultCollapsed?: boolean;        actions?: SidebarActionData[];        items?: SidebarItemData[];        content?: Snippet;    }

SidebarActionData

Source: components/src/blocks/sidebar-context.svelte.ts

export interface SidebarActionData {    id: string;    icon: string;    iconCollection?: string;    label: string;    disabled?: boolean;    onSelect?: (ownerId: string) => void;}

SidebarItemData

Source: components/src/blocks/sidebar-context.svelte.ts

export interface SidebarItemData {    id: string;    label: string;    icon?: string | Snippet;    iconCollection?: string;    depth?: 0 | 1;    href?: string;    disabled?: boolean;    updated?: boolean;    tags?: Snippet;    actions?: SidebarActionData[];}

ComponentPartsOf

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartsOf<    T extends Record<string, Component<any>>,> = {    [K in keyof T]?: ConfigurableComponentPartProps<T[K]>;};

ConfigurableComponentPartProps

Source: components/src/logic/component-parts.svelte.ts

export type ConfigurableComponentPartProps<C extends Component<any>> = Partial<    Omit<ComponentProps<C>, ComponentPartControlledKey>>;

ComponentPartControlledKey

Source: components/src/logic/component-parts.svelte.ts

export type ComponentPartControlledKey =    | "children"    | "as"    | "value"    | "defaultValue"    | "open"    | "selected"    | "checked"    | "disabled"    | "readonly"    | "id"    | `on${string}`    | `aria-${string}`;