Mischief

39 / Blocks

Signature Footer

A complete closing section with room for the useful links first and one oversized wordmark at the end.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/signature-footer
import { SignatureFooter } from "mischief-ui/signature-footer"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/signature-footer/signature-footer.tsx
import * as React from "react" import { cn } from "@/lib/utils"import {  FOOTER_LABEL,  FooterColumns,  type FooterColumn,  type FooterLink,  type FooterLinkRenderer,} from "@/registry/default/footer-columns/footer-columns"import { FooterRow } from "@/registry/default/footer-row/footer-row"import { FooterWordmark } from "@/registry/default/footer-wordmark/footer-wordmark" export type { FooterColumn, FooterLink, FooterLinkRenderer }

Usage

export function Footer() {
  return (
    <SignatureFooter
      heading={<Logo />}
      description="Short links and file sharing with real-time analytics."
      columns={[
        { label: "Product", links: [{ label: "Pricing", href: "/pricing" }] },
        { label: "Company", links: [{ label: "Docs", href: docsUrl, external: true }] },
      ]}
      related={{
        label: "Other products",
        links: otherProducts,
      }}
      renderLink={({ href, label }) => <Link href={href}>{label}</Link>}
      brand={<span>© 2026 Northstar</span>}
      legal={<LegalLinks />}
      status={<SystemStatus />}
      wordmark="northstar"
    />
  )
}

Links, and who renders them

Pass columns and the footer lays out the labelled groups and styles the links itself, so a directory of thirty links is a data structure rather than thirty lines of markup. Pass navigation instead when the shape is unusual enough that you would rather build it.

Links are plain anchors unless you say otherwise. renderLink hands each one back to you, which is how a framework's own link component gets used without the footer knowing anything about it.

renderLink={({ href, label, external }) =>
  external ? (
    <a href={href} target="_blank" rel="noreferrer noopener">{label}</a>
  ) : (
    <Link href={href}>{label}</Link>
  )
}
Styling comes back to you as well, so keep it consistent if you take this over.

related is the same shape as a column but laid out as a wrapping row above the closing line, set off by a dashed rule. It is for the links that are not part of this product -- a sister site, the rest of a portfolio -- and its label is yours to name.

Dark or light

The default is the page's foreground colour as a ground, which reads as a dark slab under a light site. Every shade inside is mixed from the footer's own text colour rather than a theme token, so setting a different background and text colour on the element is all it takes to move it to a light ground.

<SignatureFooter
  className="bg-card text-card-foreground border-border border-t"
  ...
/>
The rules, the muted copy, and the wordmark all follow the text colour.

The wordmark

The oversized word across the bottom is drawn at a fraction of the footer's own colour and clipped by the edge of the page. It is hidden from assistive technology and unselectable, because it is a texture rather than a heading -- a screen reader announcing an enormous brand name at the end of every page is noise.

Keep it to one short word. It scales with the viewport and is set to never wrap, so anything long is simply cut off rather than reflowed, and the name you actually want read belongs in brand or meta.

Filling it in

Everything except the heading and the wordmark is optional, and each slot takes whatever you give it. There is no link list baked in, no newsletter form, and no social row -- pass your own navigation and it is laid out with the rest.

  • eyebrow and heading carry the line you want people to leave with.
  • action is the single thing you want them to do next, not three things.
  • navigation takes your own list markup, so the grouping is yours.
  • brand and meta hold the small print along the bottom edge.

This is a server component: it holds no state and no effects, so it can render on the server and ship no JavaScript. Import it from its own entry to keep it that way.

API

wordmarkstringThe oversized closing brand name. The only required prop.
columnsFooterColumn[]Labelled link columns. Wins over navigation.
relatedFooterColumnA wrapping row of links set apart above the closing row.
renderLink(link: FooterLink) => ReactNodeRenders every link, for your framework's link component.
headingReactNodeA line to lead with, or a logo. Optional.
eyebrowReactNodeA short label above the heading.
descriptionReactNodeSupporting copy, held to about 36 characters a line.
socialReactNodeA row of icon links under the description.
actionReactNodeA primary link or button.
navigationReactNodeYour own markup, when the columns do not fit.
brand, metaReactNode, ReactNodeOpen the closing row: ownership and small print.
legal, statusReactNode, ReactNodeClose it: terms in the middle, a status on the right.
classNamestringClasses for the footer element. Set the ground here.

Accessibility

A semantic footer element, and a real heading when you give it one rather than an empty one when you do not. A link marked external opens in a new tab, carries rel=noreferrer noopener, and says so in its accessible name -- led by a comma, because a leading space is dropped when that name is computed. Column labels are plain text rather than headings, so a long directory does not litter the page outline. The oversized wordmark is decoration and hidden from assistive technology.