Retro Sidebar
A sidebar navigation for screens that want a terminal or retro CRT feel. Renders one or more labelled sections of links, with optional header, footer, and decor slots — shader canvases, gradients, scanline overlays — without coupling the sidebar to a specific renderer.
Customize
Layout
comfortable
plain
Slots
Installation
npx shadcn@latest add https://craftbits.dev/r/retro-sidebar.jsonUsage
RetroSidebar is a section-based nav primitive. Pass a sections array; the component renders the headings, the link lists, and the landmark wrapper.
import { RetroSidebar } from "@craft-bits/core";
<RetroSidebar
sections={[
{
label: "Archive",
items: [
{ label: "~/2026", href: "/archive/2026", current: true },
{ label: "~/2025", href: "/archive/2025" },
],
},
{
label: "Series",
items: [
{ label: "~/principles", href: "/series/principles" },
{ label: "~/playground", href: "/series/playground" },
],
},
]}
/>Pass a background canvas, shader, or gradient into the decor slot — the sidebar positions it absolute, behind the content, with pointer-events: none and aria-hidden:
<RetroSidebar
sections={sections}
decor={<ScanlineCanvas />}
/>Drop a status pill, search box, or sign-off into the header and footer slots — both are freeform ReactNodes rendered above the first and below the last section.
Understanding the component
- Section-shaped data. Each section is a label plus a list of items, and each item is a label, href, and optional
currentflag. The data shape mirrors how sidebars actually compose in practice — group by topic, link by URL, mark the current page. - Aside is a landmark. The root renders an aside with a configurable
aria-label(defaults to "Sidebar"), so screen readers announce the complementary region correctly. - Per-section nav. Each section's link list is wrapped in its own nav with
aria-labelset to the section's label, so assistive tech can jump between groups instead of treating the whole sidebar as one bag of links. - Current page hook. Items with
current: truegetaria-current="page", adata-currentattribute, and the accent color — three independent style and ARIA hooks consumers can target. - Decor is fully consumer-controlled. Terminal shaders, WebGL canvases, gradient overlays — none of them ship in the sidebar. The slot is a positioned wrapper with
pointer-events: noneandaria-hidden; the consumer supplies the renderer. - Header and footer slots. Optional ReactNode slots for a status pill, search box, theme toggle, or sign-off — composed freely by the caller, no array shape.
- Density variants.
compact,comfortable,spacioustune the inner padding for tight chrome vs. roomy panels. - Tone variants.
plainpaints the default background;mutedswitches to the muted surface token so the sidebar reads as a secondary panel against a primary canvas. - Data hooks. The root carries
data-cb-retro-sidebar,data-density, anddata-tone; each section carriesdata-cb-retro-sidebar-section; each link carriesdata-cb-retro-sidebar-nav-linkfor consumer styling without prop-drilling.
Variants
Single section
<RetroSidebar
sections={[
{
label: "Archive",
items: [
{ label: "~/2026", href: "/archive/2026" },
{ label: "~/2025", href: "/archive/2025" },
],
},
]}
/>Multiple sections with current marker
<RetroSidebar
sections={[
{
label: "Archive",
items: [
{ label: "~/2026", href: "/archive/2026", current: true },
{ label: "~/2025", href: "/archive/2025" },
],
},
{
label: "Series",
items: [
{ label: "~/principles", href: "/series/principles" },
{ label: "~/playground", href: "/series/playground" },
],
},
]}
/>Compact, muted tone
<RetroSidebar
density="compact"
tone="muted"
sections={sections}
/>With header and footer slots
<RetroSidebar
sections={sections}
header={<SearchBox />}
footer={<span>(c) 2026 — terminal_dreams</span>}
/>Props
RetroSidebar
| Prop | Type | Default | Description |
|---|---|---|---|
sections | RetroSidebarSection[] | — | Required. Labelled groups of items rendered as nav sections. |
ariaLabel | string | "Sidebar" | Landmark label on the aside element. |
header | ReactNode | — | Slot rendered above the first section. |
footer | ReactNode | — | Slot rendered below the last section. |
decor | ReactNode | — | Decorative background slot (positioned absolute, aria-hidden). |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Inner padding scale. |
tone | 'plain' | 'muted' | 'plain' | Surface tone. |
className | string | — | Merged onto the rendered aside via cn(). |
...rest | HTMLAttributes<HTMLElement> | — | Any other aside attribute. |
RetroSidebarSection
| Field | Type | Description |
|---|---|---|
label | string | Section heading. |
items | RetroSidebarItem[] | Items inside the section. |
RetroSidebarItem
| Field | Type | Description |
|---|---|---|
label | string | Display label for the link. |
href | string | Anchor href. |
current | boolean | Mark as current page. Adds aria-current="page" and accent styling. |
RetroSidebar.NavLink
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Anchor href. |
itemClassName | string | — | Override classes on the wrapping list item. |
className | string | — | Merged onto the anchor via cn(). |
...rest | AnchorHTMLAttributes<HTMLAnchorElement> | — | Any other anchor attribute. |
Accessibility
- The root renders an aside landmark with a configurable
aria-label— screen readers announce the complementary region correctly. - Each section's list is wrapped in its own nav with
aria-labelset to the section's label, so assistive tech can jump between groups. - Items marked
current: truegetaria-current="page"so screen readers announce the current location. - Links have a
:focus-visiblering tied to the accent token; color contrast on--cb-fg,--cb-fg-muted, and--cb-accentmeets WCAG AA. - The decor slot is
aria-hidden="true"withpointer-events: none, so background art never traps focus or screen-reader cursors.
Credits
- Extracted from:
terminal-dreams(src/components/retro/RetroSidebar.tsx). The original was a one-off "system status" widget — a fixed grid of four hard-coded stats bound to a singlepostsCountnumber, wrapped in a CSS module tied to terminal-dreams' theme. craft-bits keeps the underlying intent — a retro / terminal sidebar — and lifts it into a generalised section-based navigation primitive: asectionsarray of labelled link groups, plusheader,footer, anddecorslots, CVA density / tone variants,cb-*token-driven styling,forwardRef, andclassNamemerging viacn(). The status-widget shape is gone; in its place is the canonical sidebar-nav shape consumers actually need.