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.
Customize
Layout
compact
center
Slots
Installation
npx shadcn@latest add https://craftbits.dev/r/retro-footer.jsonUsage
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
- 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.
- Footer is a landmark. The root renders a footer element so assistive tech announces the contentinfo landmark.
- Copyright defaults to an EOF rule. Omitting
copyrightrenders a centered box-drawing EOF line in keeping with the terminal aesthetic. Pass any ReactNode to override; passnullto suppress. - 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: noneandaria-hidden; the consumer supplies the renderer. - Nav row is freeform. Drop any nodes you want — RetroFooter.NavLink wrappers, raw list items, custom badges. The row is
flex-wrapwith consistent gaps. - Actions slot pins right. When
actionsis 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. - Density variants.
compact,comfortable,spacioustune the vertical padding for tight chrome vs. magazine-style splash footers. - Tabular numbers. The copyright line uses
font-variant-numeric: tabular-numsso year strings line up cleanly when they update. - Data hooks. The root carries
data-cb-retro-footer,data-density, anddata-align; each nav link carriesdata-cb-retro-footer-nav-linkfor 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
| Prop | Type | Default | Description |
|---|---|---|---|
nav | ReactNode | — | Freeform nav row — usually RetroFooter.NavLink children. |
copyright | ReactNode | EOF rule | Copyright / sign-off line. Pass null to suppress. |
decor | ReactNode | — | Decorative background slot (positioned absolute, aria-hidden). |
actions | ReactNode | — | Trailing 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. |
innerClassName | string | — | Override classes on the centered inner column. |
className | string | — | Merged onto the rendered footer via cn(). |
...rest | HTMLAttributes<HTMLElement> | — | Any other footer attribute. |
RetroFooter.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 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"withpointer-events: none, so background art never traps focus or screen-reader cursors. - Nav links have a
:focus-visiblering tied to the accent token; color contrast on--cb-fg-mutedand--cb-accentmeets 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, andactionsslots, aRetroFooter.NavLinkcompound part for the link row, CVA density / align variants,cb-*token-driven styling,forwardRef, andclassNamemerging viacn(). The compound NavLink shape is new; the original had no nav row at all.