caliper uidocs

Milestones block

Block

A history read top to bottom. One line fills as the list scrolls through the window and each marker settles when it is reached, using native scroll only.

Installs components/ui/milestones-block.tsxDepends on lucide-reactMotion honours reduced motion
pnpm dlx shadcn@latest add https://caliperui.site/r/milestones-block.json

Our story

Six years of quieter month-ends

  1. March 2020

    Two accountants and a spreadsheet

    Halden starts as a set of macros for a 12-person agency's close.

  2. January 2021

    First bank feed

    Mercury and Stripe connect directly; matching saves our first customers a day a month.

  3. October 2022

    Rules and approvals

    Teams write their own rules, and large payments wait for sign-off.

    1,000 teams

  4. June 2024

    Multi-entity close

    Groups with several companies close them together, with eliminations.

  5. May 2026

    The close reads itself

    A written summary of what changed and why, generated from the ledger.

    $40B reconciled

When to use #

Use it for

  • About pages, a product's story, a funding history.
  • Five to ten dated events.

Skip it when

  • Release notes; use the changelog.
  • Anything people need to scan quickly; use a table.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/milestones-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.

example.tsx
import { MilestonesBlock } from "@/components/ui/milestones-block"

export function MilestonesBlockDemo() {
  return (
    <MilestonesBlock
      eyebrow="Our story"
      title="Six years of quieter month-ends"
      milestones={[
        { date: "March 2020", title: "Two accountants and a spreadsheet", body: "Halden starts as a set of macros for a 12-person agency's close." },
        { date: "January 2021", title: "First bank feed", body: "Mercury and Stripe connect directly; matching saves our first customers a day a month." },
        { date: "October 2022", title: "Rules and approvals", body: "Teams write their own rules, and large payments wait for sign-off.", figure: "1,000 teams" },
        { date: "June 2024", title: "Multi-entity close", body: "Groups with several companies close them together, with eliminations." },
        { date: "May 2026", title: "The close reads itself", body: "A written summary of what changed and why, generated from the ledger.", figure: "$40B reconciled" },
      ]}
    />
  );
}

const teams = [120, 180, 260, 310, 420, 540, 610, 590, 720, 980, 1240, 1380, 1520, 1880, 2310, 2640].map((value, i) => ({ label: `Q${(i % 4) + 1} ${2023 + Math.floor(i / 4)}`, value }));

API reference #

MilestonesBlock

component
PropTypeDefault
eyebrowstring—
title *string—
leadstring—
milestones *readonly Milestone[]—
classNamestring—

Accessibility #

It is built on native elements and keeps aria-hidden in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.