Footer block
BlockThe page's last word: link columns, a newsletter that confirms in place, the legal line, status and the name as a full-width wordmark.
pnpm dlx shadcn@latest add https://caliperui.site/r/footer-block.jsonWhen to use #
Use it for
- The end of every marketing page.
- You want sign-up and navigation within reach of the last section.
Skip it when
- Inside an app; use the app frame.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/footer-block.json
Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { type ReactNode } from "react" import { FooterBlock } from "@/components/ui/footer-block" function Frame({ children }: { children: ReactNode }) { return <div className="w-full overflow-hidden rounded-[14px] shadow-[0_0_0_1px_var(--border)]">{children}</div>; } const columns = (product: string[], company: string[] = ["About", "Careers", "Contact"], resources: string[] = ["Docs", "Changelog", "Status"]) => [ { title: "Product", links: product.map((label) => ({ label, href: "#" })) }, { title: "Resources", links: resources.map((label) => ({ label, href: "#" })) }, { title: "Company", links: company.map((label) => ({ label, href: "#" })) }, { title: "Legal", links: ["Privacy", "Terms", "DPA"].map((label) => ({ label, href: "#" })) }, ]; const footers: Parameters<typeof FooterBlock>[0] = { brand: "Halden", tagline: "Month-end close for finance teams. Lisbon and London.", columns: columns(["Matching", "Review", "Audit trail", "Pricing"]), newsletter: { label: "The close, monthly", note: "One email at month end. Unsubscribe in one click.", onSubmit: () => new Promise((resolve) => setTimeout(resolve, 600)) }, legal: "© 2026 Halden Ltd", status: { label: "All systems normal", href: "#" } }; export function FooterBlockDemo() { return <Frame><FooterBlock {...footers} /></Frame>; }
API reference #
Accessibility #
It exposes status semantics, with aria-hidden, aria-invalid, aria-describedby and aria-label kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.