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

Customize
Layout
comfortable
start
Slots

Installation

npx shadcn@latest add https://craftbits.dev/r/retro-header.json

Usage

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

  1. 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.
  2. 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.
  3. 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.
  4. ASCII slot is pre-formatted. The slot renders inside a <pre> with aria-hidden, so screen readers skip the banner — the heading-1 below is the accessible name.
  5. 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: none and aria-hidden; the consumer supplies the renderer.
  6. Nav row is freeform. Drop any nodes you want — RetroHeader.NavLink wrappers, raw <li> markers, custom badges. The row is flex-wrap with consistent gaps.
  7. Actions slot pins right. When actions is present, the inner column switches to items-start justify-between so a theme toggle or sign-in rides the top-right corner.
  8. Density variants. compact, comfortable, spacious tune the vertical padding for tight dashboards vs. magazine-style splashes.
  9. Data hooks. The root carries data-cb-retro-header, data-density, and data-align; each nav link carries data-cb-retro-nav-link for 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

PropTypeDefaultDescription
titleReactNodeRequired. Primary heading (renders as heading-1).
asciiReactNodeASCII banner / wordmark rendered above the title.
subtitleReactNodeShort tagline below the title.
navReactNodeFreeform nav row — usually RetroHeader.NavLink children.
decorReactNodeDecorative background slot (positioned absolute, aria-hidden).
actionsReactNodeTrailing 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.
innerClassNamestringOverride classes on the centered inner column.
classNamestringMerged onto the rendered header via cn().
...restHTMLAttributes<HTMLElement>Any other header attribute.

RetroHeader.NavLink

PropTypeDefaultDescription
hrefstringAnchor href. Pass a full URL or app-relative path.
itemClassNamestringOverride classes on the wrapping list item.
classNamestringMerged onto the anchor via cn().
...restAnchorHTMLAttributes<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" with pointer-events: none, so background art never traps focus or screen-reader cursors.
  • The blinking cursor on the title respects prefers-reduced-motion: reduce and falls back to a static block.
  • Nav links have a :focus-visible ring tied to the accent token; color contrast on --cb-fg, --cb-fg-muted, and --cb-accent meets WCAG AA.

Credits

  • Extracted from: terminal-dreams (src/components/retro/RetroHeader.tsx). The original bundled a FaultyTerminal WebGL canvas, a hand-rolled theme MutationObserver, hard-coded site nav, a ThemeToggle, an opt-in RetroAboutCard, a ScrambleHover per 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 compound NavLink shape is new.