Product · Oct 21
How we made a dashboard that explains itself
Every chart gets a sentence and a table. Here is what we learned from shipping that rule.
Maya Chen
Hero, feature and pricing sections for a landing page.
pnpm dlx shadcn@latest add https://caliperui.site/r/marketing-sections.jsonAnnouncement
Version 3 is here, with a new motion system.
See what is newNavbar
Hero, split
New: usage billing
A component library where accessibility, motion and performance are part of every piece.
Hero, centered
Fewer clicks, clearer screens and a keyboard shortcut for everything.
No card needed. Free for teams up to five.
Logo cloud
Social proof
4.8
from 1,200 reviews
“The calmest tool we use.”
Features, grid
Windowed lists and lazy canvases keep large data smooth.
Roles, audit trails and single sign-on built in.
Tokens drive light, dark and your brand.
Every control and shortcut works without a mouse.
Time zones, locales and right-to-left layouts.
Animation that explains change, then steps back.
Features, rows
A single dashboard for plans, usage and people.
Search any setting or action from the keyboard.
Content
Our approach
We start with the states people forget: empty, loading, error and offline.
Then we make sure each one is calm, clear and reachable from the keyboard.
How it works
Add only what you use to your project.
Change colours, radius and type once.
Build, test and release with confidence.
Use cases
Roadmaps, boards and release checklists share a language.
Pricing
For trying things out.
$0 forever
For teams that ship together.
$24 per seat / month
For security and scale.
Custom
Comparison
Trust
SOC 2 Type II
Audited yearly
GDPR
EU data region
SSO and SCIM
Okta, Entra, Google
99.98% uptime
Last 90 days
Stats
Testimonials
We moved the whole team in an afternoon.
The keyboard support alone was worth switching.
Finally charts that explain themselves.
Gallery
What we offer
Hundreds of pieces that fit together.
Tokens, motion and guidance.
Answers from the people who built it.
Full pages to start from.
FAQ
Yes. Copy the components you need and import them like any other file.
Everything reads a small set of CSS variables you can change.
Accessibility is a release requirement, with keyboard and reduced-motion support throughout.
Contact
We reply within a day.
hello@northwind.coWeekdays, 9 to 6 CET.
+49 30 1234 5678Usually under 5 minutes.
Start a chatNewsletter
Release notes, patterns and the occasional deep dive.
Call to action
Free for small teams. Upgrade when you are ready.
Blog
Product · Oct 21
Every chart gets a sentence and a table. Here is what we learned from shipping that rule.
Maya Chen
Engineering · Oct 14
Lena Fischer
Design · Oct 7
Noor Haddad
Company · Sep 29
Ravi Patel
Footer
pnpm dlx shadcn@latest add https://caliperui.site/r/marketing-sections.json
Adds its 8 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.
This is the code behind the preview above. It is built from HeroSplit, HeroCentered, PricingCards, MegaFooter, FeatureGrid, FeatureRows, ContentSplit, BlogFeatured, TrustBadges, AnnouncementStrip, UseCaseTabs, HowItWorks, LogoCloud, SocialProof, CounterStats, TestimonialCards, GalleryMosaic, CenteredNavbar, NewsletterCard, ContactChannels, ComparisonCards, FaqColumns, CtaSplit and OfferList.
import { BadgeCheck, Gauge, Globe, Layers, LifeBuoy, LockKeyhole, Mail, MessageCircle, Palette, Phone, ShieldCheck, Sparkles, Zap } from "lucide-react" import { useState } from "react" import { AnnouncementStrip, BlogFeatured, CenteredNavbar, ComparisonCards, ContactChannels, ContentSplit, CounterStats, CtaSplit, FaqColumns, FeatureGrid, FeatureRows, GalleryMosaic, HeroCentered, HeroSplit, HowItWorks, LogoCloud, MegaFooter, NewsletterCard, OfferList, PricingCards, SocialProof, TestimonialCards, TrustBadges, UseCaseTabs } from "@/components/ui/marketing-sections" import { Button } from "@/components/ui/button" const shot = (n: number, label: string) => ( <div className="grid w-full max-w-sm gap-2" aria-hidden="true"><p className="text-xs uppercase tracking-wide text-muted-foreground">{label}</p>{[0.9, 0.62, 0.78].map((w, i) => <span key={i} className="block h-3 rounded-full" style={{ width: `${w * 100}%`, background: `color-mix(in srgb, var(--chart-${n}) ${70 - i * 16}%, var(--background))` }} />)}<span className="mt-1 block h-20 rounded-lg" style={{ background: `color-mix(in srgb, var(--chart-${n}) 24%, var(--background))` }} /></div> ); const brand = <span className="flex items-center gap-2 text-base font-semibold"><Layers aria-hidden="true" className="size-5" />Northwind</span>; const wait = (ms: number) => new Promise((r) => setTimeout(r, ms)); const Block = ({ name, children }: { name: string; children: React.ReactNode }) => <section className="grid min-w-0 gap-1 rounded-xl border border-dashed border-border"><p className="px-4 pt-3 text-xs font-medium uppercase tracking-wide text-muted-foreground">{name}</p>{children}</section>; export function MarketingSectionsDemo() { const [bar, setBar] = useState(true); return ( <div className="grid gap-8"> {bar && <Block name="Announcement"><AnnouncementStrip action={<a href="#" className="font-medium underline underline-offset-4">See what is new</a>} onDismiss={() => setBar(false)}>Version 3 is here, with a new motion system.</AnnouncementStrip></Block>} <Block name="Navbar"><CenteredNavbar brand={brand} links={[{ label: "Product", href: "#" }, { label: "Pricing", href: "#" }, { label: "Docs", href: "#" }, { label: "Blog", href: "#" }]} actions={<><Button variant="ghost" size="sm">Sign in</Button><Button size="sm">Get started</Button></>} /></Block> <Block name="Hero, split"><HeroSplit eyebrow="New: usage billing" title="Ship interfaces people trust" description="A component library where accessibility, motion and performance are part of every piece." actions={<><Button size="lg">Start free</Button><Button size="lg" variant="secondary">Read the docs</Button></>} media={shot(1, "Dashboard")} /></Block> <Block name="Hero, centered"><HeroCentered badge={<span className="rounded-full border border-border px-3 py-1 text-xs text-muted-foreground">Now in public beta</span>} title="Calm software for busy teams" description="Fewer clicks, clearer screens and a keyboard shortcut for everything." actions={<><Button size="lg">Try it free</Button><Button size="lg" variant="ghost">Book a demo</Button></>} footnote="No card needed. Free for teams up to five." /></Block> <Block name="Logo cloud"><LogoCloud title="Trusted by teams at" logos={["Brightlabs", "Harbour", "Atlas", "Quill", "Nimbus", "Orbit"].map((n) => ({ id: n, name: n }))} /></Block> <Block name="Social proof"><SocialProof rating={4.8} count="1,200 reviews" people={["Maya Chen", "Ravi Patel", "Noor Haddad", "Kofi Mensah", "Lena Fischer"]} quote="The calmest tool we use." /></Block> <Block name="Features, grid"><FeatureGrid title="Everything a product team needs, nothing it does not" features={[{ id: "a", icon: <Gauge />, title: "Fast", body: "Windowed lists and lazy canvases keep large data smooth." }, { id: "b", icon: <ShieldCheck />, title: "Secure", body: "Roles, audit trails and single sign-on built in." }, { id: "c", icon: <Palette />, title: "Themeable", body: "Tokens drive light, dark and your brand." }, { id: "d", icon: <Zap />, title: "Keyboard first", body: "Every control and shortcut works without a mouse." }, { id: "e", icon: <Globe />, title: "Global", body: "Time zones, locales and right-to-left layouts." }, { id: "f", icon: <Sparkles />, title: "Motion", body: "Animation that explains change, then steps back." }]} /></Block> <Block name="Features, rows"><FeatureRows features={[{ id: "r1", title: "See everything in one place", body: "A single dashboard for plans, usage and people.", media: shot(2, "Overview") }, { id: "r2", title: "Move faster with shortcuts", body: "Search any setting or action from the keyboard.", media: shot(3, "Command") }]} /></Block> <Block name="Content"><ContentSplit eyebrow="Our approach" title="Design with the details in mind" media={shot(4, "Details")}><p>We start with the states people forget: empty, loading, error and offline.</p><p>Then we make sure each one is calm, clear and reachable from the keyboard.</p></ContentSplit></Block> <Block name="How it works"><HowItWorks title="Up and running in three steps" steps={[{ id: "1", title: "Copy the components", body: "Add only what you use to your project." }, { id: "2", title: "Set your tokens", body: "Change colours, radius and type once." }, { id: "3", title: "Ship", body: "Build, test and release with confidence." }]} /></Block> <Block name="Use cases"><UseCaseTabs cases={[{ id: "p", label: "Product teams", title: "Plan, build and release in one flow", body: "Roadmaps, boards and release checklists share a language.", points: ["Release readiness checks", "Approvals with a paper trail", "Board and list views"], media: shot(1, "Board") }, { id: "s", label: "Support", title: "Answer faster with context", body: "See the customer, the plan and the history side by side.", points: ["Saved replies", "SLA timers", "Private notes"], media: shot(2, "Ticket") }, { id: "f", label: "Finance", title: "Bills that explain themselves", body: "Usage, tiers and totals shown line by line.", points: ["Usage forecasts", "Budget alerts", "Invoice editor"], media: shot(3, "Invoice") }]} /></Block> <Block name="Pricing"><PricingCards tiers={[{ id: "s", name: "Starter", price: "$0", period: "forever", description: "For trying things out.", cta: "Start free", features: ["Up to 3 projects", "Community support", "Basic themes"] }, { id: "t", name: "Team", price: "$24", period: "per seat / month", description: "For teams that ship together.", cta: "Start a trial", featured: true, features: ["Unlimited projects", "Single sign-on", "90 days of history", "Priority support"] }, { id: "e", name: "Enterprise", price: "Custom", description: "For security and scale.", cta: "Contact sales", features: ["Dedicated region", "Audit log export", "Custom roles", "Named contact"] }]} /></Block> <Block name="Comparison"><ComparisonCards title="Why teams switch" us={{ name: "Northwind", points: ["Accessible by default", "Keyboard everywhere", "One calm design language", "You own the code"] }} them={{ name: "A typical kit", points: ["Accessibility as an add-on", "Mouse-first menus", "Mixed visual styles", "Locked-in packages"] }} /></Block> <Block name="Trust"><TrustBadges title="Security you can check" badges={[{ id: "a", label: "SOC 2 Type II", detail: "Audited yearly", icon: <BadgeCheck /> }, { id: "b", label: "GDPR", detail: "EU data region", icon: <Globe /> }, { id: "c", label: "SSO and SCIM", detail: "Okta, Entra, Google", icon: <LockKeyhole /> }, { id: "d", label: "99.98% uptime", detail: "Last 90 days", icon: <ShieldCheck /> }]} /></Block> <Block name="Stats"><CounterStats stats={[{ id: "a", value: 12400, label: "teams", suffix: "+" }, { id: "b", value: 99.98, label: "uptime", suffix: "%" }, { id: "c", value: 38, label: "countries" }, { id: "d", value: 4.8, label: "average rating" }]} /></Block> <Block name="Testimonials"><TestimonialCards title="Loved by people who ship" items={[{ id: "1", quote: "We moved the whole team in an afternoon.", name: "Maya Chen", role: "Platform lead" }, { id: "2", quote: "The keyboard support alone was worth switching.", name: "Ravi Patel", role: "Product manager" }, { id: "3", quote: "Finally charts that explain themselves.", name: "Noor Haddad", role: "Designer" }]} /></Block> <Block name="Gallery"><GalleryMosaic items={[{ id: "1", label: "Dashboard", size: "wide", node: shot(1, "Dashboard") }, { id: "2", label: "Board", node: shot(2, "Board") }, { id: "3", label: "Editor", size: "tall", node: shot(3, "Editor") }, { id: "4", label: "Charts", node: shot(4, "Charts") }, { id: "5", label: "Settings", node: shot(5, "Settings") }]} /></Block> <Block name="What we offer"><OfferList title="What we offer" items={[{ id: "1", icon: <Layers />, title: "Components", body: "Hundreds of pieces that fit together." }, { id: "2", icon: <Palette />, title: "Design system", body: "Tokens, motion and guidance." }, { id: "3", icon: <LifeBuoy />, title: "Support", body: "Answers from the people who built it." }, { id: "4", icon: <Sparkles />, title: "Templates", body: "Full pages to start from." }]} /></Block> <Block name="FAQ"><FaqColumns title="Questions, answered" items={[{ id: "1", q: "Can I use it in an existing app?", a: "Yes. Copy the components you need and import them like any other file." }, { id: "2", q: "Does it work with my design tokens?", a: "Everything reads a small set of CSS variables you can change." }, { id: "3", q: "Is it accessible?", a: "Accessibility is a release requirement, with keyboard and reduced-motion support throughout." }]} /></Block> <Block name="Contact"><ContactChannels title="Talk to a person" channels={[{ id: "e", icon: <Mail />, label: "Email", detail: "We reply within a day.", action: "hello@northwind.co", href: "mailto:hello@northwind.co" }, { id: "p", icon: <Phone />, label: "Phone", detail: "Weekdays, 9 to 6 CET.", action: "+49 30 1234 5678", href: "tel:+493012345678" }, { id: "c", icon: <MessageCircle />, label: "Chat", detail: "Usually under 5 minutes.", action: "Start a chat", href: "#" }]} /></Block> <Block name="Newsletter"><NewsletterCard title="One useful email a month" description="Release notes, patterns and the occasional deep dive." onSubmit={async () => { await wait(700); }} /></Block> <Block name="Call to action"><CtaSplit title="Start building something calm" description="Free for small teams. Upgrade when you are ready." actions={<><Button size="lg" variant="secondary">Get started</Button><Button size="lg" variant="ghost" className="text-background hover:bg-background/15">Talk to sales</Button></>} media={shot(5, "Preview")} /></Block> <Block name="Blog"><BlogFeatured featured={{ id: "f", tag: "Product", date: "Oct 21", title: "How we made a dashboard that explains itself", excerpt: "Every chart gets a sentence and a table. Here is what we learned from shipping that rule.", author: "Maya Chen", media: shot(2, "Article") }} posts={[{ id: "1", tag: "Engineering", date: "Oct 14", title: "Virtualising 50,000 rows without losing the keyboard", author: "Lena Fischer" }, { id: "2", tag: "Design", date: "Oct 7", title: "Motion that explains, then gets out of the way", author: "Noor Haddad" }, { id: "3", tag: "Company", date: "Sep 29", title: "What our uptime page does and does not say", author: "Ravi Patel" }]} /></Block> <Block name="Footer"><MegaFooter brand={brand} blurb="Calm, accessible software for teams who ship." columns={[{ title: "Product", links: [{ label: "Features", href: "#" }, { label: "Pricing", href: "#" }, { label: "Changelog", href: "#" }] }, { title: "Company", links: [{ label: "About", href: "#" }, { label: "Careers", href: "#" }, { label: "Contact", href: "#" }] }, { title: "Resources", links: [{ label: "Docs", href: "#" }, { label: "Blog", href: "#" }, { label: "Status", href: "#" }] }, { title: "Legal", links: [{ label: "Privacy", href: "#" }, { label: "Terms", href: "#" }, { label: "Security", href: "#" }] }]} legal="© 2025 Northwind. All rights reserved." onSubscribe={async () => { await wait(500); }} /></Block> </div> ); }
It exposes status, region, tablist, tab, tabpanel, img and alert semantics, with aria-hidden, aria-label, aria-selected, aria-controls, aria-labelledby and aria-expanded kept in step with state. Changes that matter are announced through a live region.
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.