52 / Blocks
Footer Columns
Labelled columns of links, with the column count and the link rendering left to you.
"use client"
import { DemoVariants } from "@/components/demos/demo-variants"
import {
FooterColumns,
type FooterColumn,
} from "mischief-ui/footer-columns"
const groups: FooterColumn[] = [
{
label: "Product",
links: [
{ label: "Pricing", href: "#" },
{ label: "Changelog", href: "#" },
],
},
{
label: "Tools",
links: [
{ label: "QR codes", href: "#" },
{ label: "UTM builder", href: "#" },
],
},
{
label: "Company",
links: [
{ label: "Brand", href: "#" },
{ label: "Docs", href: "#", external: true },
],
},
{
label: "Legal",
links: [
{ label: "Terms", href: "#" },
{ label: "Privacy", href: "#" },
],
},
]
export function FooterColumnsDemo() {
return (
<DemoVariants
label="Columns"
variants={[
{
id: "three",
label: "Three",
render: () => (
<div className="w-full max-w-2xl">
<FooterColumns columns={groups.slice(0, 3)} />
</div>
),
},
{
id: "four",
label: "Four",
render: () => (
<div className="w-full max-w-2xl">
<FooterColumns columnCount={4} columns={groups} />
</div>
),
},
]}
/>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/footer-columnsimport { FooterColumns } from "mischief-ui/footer-columns"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
import * as React from "react" import { cn } from "@/lib/utils" export type FooterLink = { label: React.ReactNode href: string /** Opens in a new tab, and says so. */ external?: boolean} export type FooterColumn = { label?: React.ReactNode links: readonly FooterLink[]Usage
export function Links() {
return (
<FooterColumns
columnCount={4}
columns={groups}
renderLink={({ href, label }) => <Link href={href}>{label}</Link>}
/>
)
}How many columns
One column below the small breakpoint, two above it, and columnCount at the widest. Six groups in a three-column grid is a different footer from six groups in a row, and only you know which one you meant.
The count travels as a custom property rather than a class, because a class assembled from a variable is never generated: Tailwind reads class names as literal text.
<FooterColumns columnCount={6} columns={groups} />Who renders the links
Links are plain anchors unless you say otherwise, and one marked external opens in a new tab, carries rel=noreferrer noopener, and says so in its accessible name. renderLink hands each one back instead, which is how a framework's link gets used without this component knowing about it -- along with the styling, which then becomes yours to keep consistent.
API
columnsFooterColumn[]The groups, each with an optional label and its links.columnCountnumberColumns at the widest size. Defaults to 3.renderLink(link: FooterLink) => ReactNodeRenders every link, for your framework's link component.FooterColumn
labelReactNodeThe small uppercase heading. Omit for an unlabelled group.linksFooterLink[]The links in the column, in order.FooterLink
labelReactNodeThe link text.hrefstringWhere it goes.externalbooleanOpens in a new tab, and says so to a screen reader.Accessibility
Each group is a list, so a screen reader announces how many links it holds before reading them. Labels are plain text rather than headings, so a long directory does not litter the page outline. An external link says where it goes in its accessible name, led by a comma because a leading space is dropped when that name is computed.