UI Library

Footer columns

The full marketing footer: a brand blurb, three link columns, and a legal row split off below a rule. Collapses to two columns on tablet and one on phones, with the legal row stacking rather than wrapping raggedly.

Navigationfootercolumnsresponsive
Preview
apps/site/blocks/components/FooterColumns.tsx
export interface FooterColumn {
  heading: string;
  links: { label: string; href: string }[];
}

const DEFAULT_COLUMNS: FooterColumn[] = [
  {
    heading: 'Product',
    links: [
      { label: 'Overview', href: '#' },
      { label: 'Pricing', href: '#' },
      { label: 'Changelog', href: '#' },
      { label: 'Roadmap', href: '#' },
    ],
  },
  {
    heading: 'Resources',
    links: [
      { label: 'Documentation', href: '#' },
      { label: 'API reference', href: '#' },
      { label: 'Support', href: '#' },
    ],
  },
  {
    heading: 'Legal',
    links: [
      { label: 'Privacy', href: '#' },
      { label: 'Terms', href: '#' },
      { label: 'Security', href: '#' },
    ],
  },
];

export function FooterColumns({
  brand = 'Acme',
  blurb = 'Small, focused tools that respect your data. Open-source where it counts.',
  columns = DEFAULT_COLUMNS,
  note = 'All rights reserved.',
}: {
  brand?: string;
  blurb?: string;
  columns?: FooterColumn[];
  note?: string;
}) {
  // Computed at render. In a static export this is the BUILD year, which is the
  // honest value for a pre-rendered page — a client-side Date would hydrate to
  // a different string and warn.
  const year = new Date().getFullYear();

  return (
    <footer className="border-t border-border/60 bg-background">
      <div className="mx-auto max-w-6xl px-4 py-12 sm:px-6">
        <div className="grid gap-10 sm:grid-cols-2 md:grid-cols-4">
          <div className="space-y-3">
            <div className="text-sm font-semibold">{brand}</div>
            <p className="text-sm text-muted-foreground">{blurb}</p>
          </div>

          {columns.map((column) => (
            <div key={column.heading} className="space-y-3">
              <div className="text-sm font-semibold">{column.heading}</div>
              <ul className="space-y-2 text-sm text-muted-foreground">
                {column.links.map((link) => (
                  <li key={link.label}>
                    <a href={link.href} className="transition-colors hover:text-foreground">
                      {link.label}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ))}
        </div>

        <div className="mt-10 flex flex-col gap-3 border-t border-border/60 pt-6 text-sm text-muted-foreground sm:flex-row sm:items-center sm:justify-between">
          <div>
            © {year} {brand}. {note}
          </div>
          <div className="flex items-center gap-4">
            <a href="#" className="transition-colors hover:text-foreground">Status</a>
            <a href="#" className="transition-colors hover:text-foreground">Contact</a>
          </div>
        </div>
      </div>
    </footer>
  );
}

Dependencies

None. This block is self-contained — plain React and Tailwind utility classes. Paste the file and it renders.

Colours come from CSS variables (--background, --primary, --border) using the shadcn/ui token names, so the block adopts your palette rather than importing ours.