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.