Retro Footer

A page footer for screens that want a terminal or retro CRT feel. Renders a top rule, an optional row of nav links, an EOF-style copyright line, and an optional decorative background slot — shader canvases, gradients, scanline overlays — without coupling the footer to a specific renderer.

──────────────── [EOF] // (c) 2026 ────────────────

Customize
Layout
compact
center
Slots

Installation

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

Usage

RetroFooter is a Radix-style compound. The root paints the footer bar; RetroFooter.NavLink is one entry in the nav row.

import { RetroFooter } from "@craft-bits/core";
 
<RetroFooter
  nav={
    <>
      <RetroFooter.NavLink href="/rss">~/rss</RetroFooter.NavLink>
      <RetroFooter.NavLink href="/colophon">~/colophon</RetroFooter.NavLink>
      <RetroFooter.NavLink href="/contact">~/contact</RetroFooter.NavLink>
    </>
  }
  copyright="© 2026 — terminal_dreams"
/>

Pass a background canvas, shader, or gradient into the decor slot — the footer positions it absolute, behind the content, with pointer-events: none and aria-hidden:

<RetroFooter
  decor={<ScanlineCanvas />}
  copyright="© 2026"
/>

Skip the nav row or copyright by omitting the corresponding prop. The copyright defaults to a retro EOF rule; pass null to suppress it entirely.

Understanding the component

  1. Compound parts. RetroFooter is the bar; RetroFooter.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. Footer is a landmark. The root renders a footer element so assistive tech announces the contentinfo landmark.
  3. Copyright defaults to an EOF rule. Omitting copyright renders a centered box-drawing EOF line in keeping with the terminal aesthetic. Pass any ReactNode to override; pass null to suppress.
  4. Decor is fully consumer-controlled. Terminal shaders, WebGL canvases, gradient overlays — none of them ship in the footer. The slot is a positioned wrapper with pointer-events: none and aria-hidden; the consumer supplies the renderer.
  5. Nav row is freeform. Drop any nodes you want — RetroFooter.NavLink wrappers, raw list items, custom badges. The row is flex-wrap with consistent gaps.
  6. Actions slot pins right. When actions is present, the inner column switches to a side-by-side layout so a back-to-top button or status pill rides the right edge on wide screens.
  7. Density variants. compact, comfortable, spacious tune the vertical padding for tight chrome vs. magazine-style splash footers.
  8. Tabular numbers. The copyright line uses font-variant-numeric: tabular-nums so year strings line up cleanly when they update.
  9. Data hooks. The root carries data-cb-retro-footer, data-density, and data-align; each nav link carries data-cb-retro-footer-nav-link for consumer styling without prop-drilling.

Variants

Bare — default EOF line

<RetroFooter />

Nav + copyright

<RetroFooter
  nav={
    <>
      <RetroFooter.NavLink href="/rss">~/rss</RetroFooter.NavLink>
      <RetroFooter.NavLink href="/colophon">~/colophon</RetroFooter.NavLink>
    </>
  }
  copyright="© 2026"
/>

Spacious, left-aligned

<RetroFooter
  density="spacious"
  align="start"
  copyright="// the brief, the bar, the bits"
/>

With actions

<RetroFooter
  copyright="© 2026"
  actions={<BackToTopButton />}
/>

Props

RetroFooter

PropTypeDefaultDescription
navReactNodeFreeform nav row — usually RetroFooter.NavLink children.
copyrightReactNodeEOF ruleCopyright / sign-off line. Pass null to suppress.
decorReactNodeDecorative background slot (positioned absolute, aria-hidden).
actionsReactNodeTrailing slot — usually a back-to-top or status pill.
density'compact' | 'comfortable' | 'spacious''compact'Vertical padding scale.
align'start' | 'center''center'Text alignment of the inner column.
innerClassNamestringOverride classes on the centered inner column.
classNamestringMerged onto the rendered footer via cn().
...restHTMLAttributes<HTMLElement>Any other footer attribute.

RetroFooter.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 footer landmark — screen readers announce the contentinfo region.
  • The nav slot is wrapped in <nav aria-label="Footer"> with a list semantic; each NavLink wraps its anchor in a list item.
  • The decor slot is aria-hidden="true" with pointer-events: none, so background art never traps focus or screen-reader cursors.
  • Nav links have a :focus-visible ring tied to the accent token; color contrast on --cb-fg-muted and --cb-accent meets WCAG AA.
  • All animations are CSS-only and respect prefers-reduced-motion: reduce — there is no JS-driven motion in the footer body.

Credits

  • Extracted from: terminal-dreams (src/components/retro/RetroFooter.tsx). The original was a one-line footer baking in a hard-coded EOF copyright string and a CSS-module pair tied to terminal-dreams' theme. craft-bits keeps the underlying intent — a retro / terminal page footer — and lifts it into a slotted compound: nav, copyright, decor, and actions slots, a RetroFooter.NavLink compound part for the link row, CVA density / align variants, cb-* token-driven styling, forwardRef, and className merging via cn(). The compound NavLink shape is new; the original had no nav row at all.