Retro Header
A page header for screens that want a terminal or retro CRT feel. Renders an optional ASCII banner, a blinking-cursor display title, a tagline subtitle, and a freeform navigation row. A separate decor slot accepts any background art — shader canvases, gradients, scanline overlays — without coupling the header to a specific renderer.
TERMINAL_DREAMS
// Nostalgic bytes from the digital underground
Installation
npx shadcn@latest add https://craftbits.dev/r/retro-header.jsonUsage
RetroHeader is a Radix-style compound. The root paints the header bar; RetroHeader.NavLink is one entry in the nav row.
import { RetroHeader } from "@craft-bits/core";
<RetroHeader
title="TERMINAL_DREAMS"
subtitle="// Nostalgic bytes from the digital underground"
nav={
<>
<RetroHeader.NavLink href="/archive">~/archive</RetroHeader.NavLink>
<RetroHeader.NavLink href="/about">~/about</RetroHeader.NavLink>
<RetroHeader.NavLink href="/playground">~/playground</RetroHeader.NavLink>
</>
}
/>Pass an ASCII banner above the title — the ascii slot renders inside a <pre> so multi-line strings preserve their layout. Drop a background canvas, shader, or gradient into the decor slot — the header positions it absolute, behind the content, with pointer-events: none and aria-hidden:
<RetroHeader
title="TERMINAL"
decor={<ScanlineCanvas />}
/>Skip the ASCII, subtitle, nav row, or decor by omitting the corresponding prop — everything but title is optional.
Understanding the component
- Compound parts. RetroHeader is the bar; RetroHeader.NavLink is one entry inside the nav slot. Composing the row rather than passing an array means each link owns its own styling and an opt-out is just deleting the JSX node.
- Title is a heading-1. Page headers are top-level landmarks — assistive tech announces the title as the document heading. Pass any ReactNode to render a custom mark inside.
- Blinking cursor on the title. A pseudo-element after the title pulses at 1s steps to mimic a terminal cursor. The animation is
motion-reduce-aware and falls back to a static block when reduced motion is on. - ASCII slot is pre-formatted. The slot renders inside a
<pre>witharia-hidden, so screen readers skip the banner — the heading-1 below is the accessible name. - Decor is fully consumer-controlled. Terminal shaders, WebGL canvases, gradient overlays — none of them ship in the header. The slot is a positioned wrapper with
pointer-events: noneandaria-hidden; the consumer supplies the renderer. - Nav row is freeform. Drop any nodes you want — RetroHeader.NavLink wrappers, raw
<li>markers, custom badges. The row isflex-wrapwith consistent gaps. - Actions slot pins right. When
actionsis present, the inner column switches toitems-start justify-betweenso a theme toggle or sign-in rides the top-right corner. - Density variants.
compact,comfortable,spacioustune the vertical padding for tight dashboards vs. magazine-style splashes. - Data hooks. The root carries
data-cb-retro-header,data-density, anddata-align; each nav link carriesdata-cb-retro-nav-linkfor consumer styling without prop-drilling.
Variants
Bare — title only
<RetroHeader title="UPLINK" />Title + nav
<RetroHeader
title="UPLINK"
nav={
<>
<RetroHeader.NavLink href="/posts">~/posts</RetroHeader.NavLink>
<RetroHeader.NavLink href="/now">~/now</RetroHeader.NavLink>
</>
}
/>Centered, spacious
<RetroHeader
align="center"
density="spacious"
title="WELCOME"
subtitle="// the brief, the bar, the bits"
/>With actions
<RetroHeader
title="DASHBOARD"
subtitle="// admin tools"
actions={<ThemeToggle />}
/>Props
RetroHeader
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Required. Primary heading (renders as heading-1). |
ascii | ReactNode | — | ASCII banner / wordmark rendered above the title. |
subtitle | ReactNode | — | Short tagline below the title. |
nav | ReactNode | — | Freeform nav row — usually RetroHeader.NavLink children. |
decor | ReactNode | — | Decorative background slot (positioned absolute, aria-hidden). |
actions | ReactNode | — | Trailing slot — usually a theme toggle or sign-in. |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Vertical padding scale. |
align | 'start' | 'center' | 'start' | Text alignment of the title column. |
innerClassName | string | — | Override classes on the centered inner column. |
className | string | — | Merged onto the rendered header via cn(). |
...rest | HTMLAttributes<HTMLElement> | — | Any other header attribute. |
RetroHeader.NavLink
| Prop | Type | Default | Description |
|---|---|---|---|
href | string | — | Anchor href. Pass a full URL or app-relative path. |
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 a header landmark and the title is a heading-1 — screen readers announce the page heading correctly.
- The nav slot is wrapped in
<nav aria-label="Primary">with a list semantic; each NavLink wraps its anchor in a list item. - The ASCII banner is
aria-hidden="true"— assistive tech reads the heading, not the box-drawing characters. - The decor slot is
aria-hidden="true"withpointer-events: none, so background art never traps focus or screen-reader cursors. - The blinking cursor on the title respects
prefers-reduced-motion: reduceand falls back to a static block. - Nav links have a
:focus-visiblering tied to the accent token; color contrast on--cb-fg,--cb-fg-muted, and--cb-accentmeets WCAG AA.
Credits
- Extracted from:
terminal-dreams(src/components/retro/RetroHeader.tsx). The original bundled aFaultyTerminalWebGL canvas, a hand-rolled theme MutationObserver, hard-coded site nav, aThemeToggle, an opt-inRetroAboutCard, aScrambleHoverper link, and an inline CSS-module flicker / glitch / blink keyframe stack. craft-bits keeps the underlying intent — a retro / terminal page header — and strips every site-specific concern. The shader, theme observer, scramble effect, about-card, theme toggle, and hard-coded nav are gone; in their place are slots (ascii,subtitle,nav,decor,actions) the consumer composes freely. The compoundNavLinkshape is new.