Sidebar

A persistent layout nav panel, distinct from Drawer (which overlays content and dismisses). A Sidebar never overlays: it collapses IN PLACE to a narrow icon rail.

Real use · app shell (collapsible, cross-tree trigger)

dashboard

The trigger above lives OUTSIDE the sidebar (in this main content area) yet still toggles it — both triggers share one SidebarProvider.

Using Sidebar

Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.

Plain HTML — CSS classes

The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Sidebar is styled but inert: its behavior comes from sidebar. For that behavior with no framework, use the node-tree path below.

/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";

Root class sidebar, with 19 modifiers:

  • .sidebar
  • .sidebar-accent
  • .sidebar-content
  • .sidebar-error
  • .sidebar-footer
  • .sidebar-group
  • .sidebar-group-label
  • .sidebar-header
  • .sidebar-header-brand
  • .sidebar-info
  • .sidebar-item
  • .sidebar-item-icon
  • .sidebar-item-label
  • .sidebar-item-trailing
  • .sidebar-neutral
  • .sidebar-primary
  • .sidebar-secondary
  • .sidebar-success
  • .sidebar-trigger
  • .sidebar-warning

React

Typed components over those same classes, with behavior and accessibility from Base UI.

npm i @wizeworks/silicaui-react
import { Sidebar } from "@wizeworks/silicaui-react";

Props

SidebarProviderProps
PropTypeRequiredDescription
childrenReact.ReactNodeyes—
collapsedbooleannoControlled collapsed state.
defaultCollapsedbooleannoUncontrolled initial collapsed state. Default false.
onCollapsedChange(collapsed: boolean) => voidno—
SidebarProps
PropTypeRequiredDescription
sideSidebarSidenoWhich edge the panel sits on. Default left.
colorSilicaColornoAccent color for the active SidebarItem; maps to sidebar-<color>.
collapsedbooleannoExplicit collapsed override. When omitted, reads the ancestor SidebarProvider (defaults to expanded if neither is present).
SidebarItemProps
PropTypeRequiredDescription
iconReact.ReactNodenoLeading icon.
trailingReact.ReactNodenoTrailing content (badge, chevron); hidden when collapsed.
activebooleannoHighlights the row with the sidebar's accent color.
disabledbooleanno—
asReact.ElementTypenoRender as a different element, typically "a" (or a router's Link) for real navigation — AllHTMLAttributes (not the plain HTMLAttributes most components here use) is deliberate so href/target/etc.
renderReact.ReactElementnoRender as a different element, composing your props with this component's — the same render Button/Badge/Card take, and Base UI's own composition model: <SidebarItem render={<Link href="/shipments" />}>…</SidebarItem> Preferred over as, because the element carries its OWN props and they are type-checked against it (SidebarItem cannot know that Link needs an href).

Source of the demo above

The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.

Show source
import { useState } from "react";
import {
    SidebarProvider,
    Sidebar,
    SidebarHeader,
    SidebarHeaderBrand,
    SidebarContent,
    SidebarFooter,
    SidebarGroup,
    SidebarGroupLabel,
    SidebarItem,
    SidebarTrigger,
    Wordmark,
    Avatar,
} from "@wizeworks/silicaui-react";
import { Section } from "../lib/Section";

const HomeIcon = (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
        <path d="M3 12 12 3l9 9" /> <path d="M5 10v10h14V10" />
    </svg>
);
const SearchIcon = (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
        <circle cx="11" cy="11" r="8" /> <path d="m21 21-4.3-4.3" />
    </svg>
);
const UsersIcon = (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
        <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /> <circle cx="9" cy="7" r="4" />
        <path d="M22 21v-2a4 4 0 0 0-3-3.87" /> <path d="M16 3.13a4 4 0 0 1 0 7.75" />
    </svg>
);
const SettingsIcon = (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
        <path d="M14 17H5" /> <path d="M19 7h-9" /> <circle cx="17" cy="17" r="3" /> <circle cx="7" cy="7" r="3" />
    </svg>
);

function AppShell() {
    const [active, setActive] = useState("dashboard");

    return (
        <div className="flex h-[26rem] w-full max-w-2xl overflow-hidden rounded-box border border-base-200">
            <Sidebar>
                <SidebarHeader>
                    <SidebarHeaderBrand>
                        <Wordmark size="sm" color="primary">Acme</Wordmark>
                    </SidebarHeaderBrand>
                    <SidebarTrigger />
                </SidebarHeader>
                <SidebarContent>
                    <SidebarGroup>
                        <SidebarGroupLabel>Workspace</SidebarGroupLabel>
                        <SidebarItem
                            icon={HomeIcon}
                            active={active === "dashboard"}
                            onClick={() => setActive("dashboard")}
                        >
                            Dashboard
                        </SidebarItem>
                        <SidebarItem
                            icon={SearchIcon}
                            active={active === "search"}
                            onClick={() => setActive("search")}
                        >
                            Search
                        </SidebarItem>
                        <SidebarItem
                            icon={UsersIcon}
                            active={active === "team"}
                            onClick={() => setActive("team")}
                            trailing={<span className="badge badge-primary badge-xs">4</span>}
                        >
                            Team
                        </SidebarItem>
                    </SidebarGroup>
                    <SidebarGroup>
                        <SidebarGroupLabel>Account</SidebarGroupLabel>
                        <SidebarItem
                            icon={SettingsIcon}
                            active={active === "settings"}
                            onClick={() => setActive("settings")}
                        >
                            Settings
                        </SidebarItem>
                    </SidebarGroup>
                </SidebarContent>
                <SidebarFooter>
                    <SidebarItem icon={<Avatar size="xs">AL</Avatar>}>Ada Lovelace</SidebarItem>
                </SidebarFooter>
            </Sidebar>

            <div className="flex flex-1 flex-col gap-3 overflow-y-auto p-6">
                <div className="flex items-center gap-2">
                    <SidebarTrigger />
                    <h4 className="text-sm font-semibold capitalize">{active}</h4>
                </div>
                <p className="text-sm opacity-70">
                    The trigger above lives OUTSIDE the sidebar (in this main content
                    area) yet still toggles it — both triggers share one
                    SidebarProvider.
                </p>
            </div>
        </div>
    );
}

export function SidebarDemo() {
    return (
        <Section title="Real use · app shell (collapsible, cross-tree trigger)">
            <SidebarProvider>
                <AppShell />
            </SidebarProvider>
        </Section>
    );
}

Vanilla — node tree to HTML

Sidebar — a persistent nav panel (<aside>) that collapses in place to an icon rail, unlike Drawer (which overlays and dismisses). Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.

npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviors
import { atom, toHtml } from "@wizeworks/silicaui-html";

const node = atom("Sidebar", "sidebar", { /* props */ }, [/* children */]);
const markup = toHtml(node);

Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "sidebar sidebar-primary" rather than passing a colour as a prop.

Takes children
yes
Hydrated by
sidebar

Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.

The full API for Sidebar, on every path, is available through the Silica MCP server.