# Checkpoint Feature

> An approvals section for a payments product: three points down the left, and on the right payouts clearing a policy one barrier at a time. A large payout waits for sign-off, so a visitor can approve or deny it, and one to a bank outside the allowlist is turned away.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/checkpoint-feature).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/checkpoint-feature
- Screenshot: https://shadcn.exchange/shot/keyframeui/checkpoint-feature/light.png
- Registry item: https://keyframeui.com/r/checkpoint-feature.json
- Categories: blocks, feature-sections
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/checkpoint-feature.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/checkpoint.json`

## Notes from the registry

Live preview and the components it's built with: https://keyframeui.com/blocks/feature-sections/checkpoint-feature

## Files

### `registry/blocks/checkpoint-feature.tsx`

```tsx
"use client";

import * as React from "react";

import { Checkpoint, type CheckpointRequest, type CheckpointRule } from "@/components/ui/checkpoint";

/* ── Content ─────────────────────────────────────────────────
 * Everything you'd change to make the section yours: the copy,
 * the points down the left, and the payouts policy the requests
 * on the right have to clear. A rule a request doesn't mention
 * passes; "hold" waits for sign-off and "block" turns it away.
 * ─────────────────────────────────────────────────────────── */

/** The one accent: the checks, the barriers' token, the primary link. */
const ACCENT = "#ff3b1f";
const ACCENT_DARK = "#ff5a3c";

const EYEBROW = "Approvals";
const HEADLINE = "Payouts wait for the right yes.";
const SUBLINE =
  "Write your money-movement policy once. Every payout and refund clears it before it runs, and anything over the line waits for a second approver.";
const POINTS = [
  { title: "Rules in plain words", body: "Amounts, payees, countries and limits, set per team and per environment." },
  { title: "Sign off where you work", body: "Approve or deny from the dashboard, Slack or email, with the reason attached." },
  { title: "Every decision on the record", body: "Who asked, which rule stopped it and who said yes, exported for your auditors." },
];
const CTA = { label: "See the policy builder", href: "#policies" };

const PANEL = { title: "Payouts policy", scope: "Production · 4 rules" };
const APPROVER = "Marta, CFO";
/** ms a held payout waits before the demo approves it. Visitors can approve or deny first. */
const APPROVE_AFTER = 3200;

const RULES: CheckpointRule[] = [
  { id: "payee", label: "Payee", detail: "A verified business", value: "VERIFIED" },
  { id: "amount", label: "Amount", detail: "Over $10,000 needs a second approver", value: "≤ $10K" },
  { id: "country", label: "Bank country", detail: "On your allowlist", value: "ALLOWLIST" },
  { id: "velocity", label: "Velocity", detail: "Three payouts a day per payee", value: "≤ 3 / DAY" },
];

const REQUESTS: CheckpointRequest[] = [
  { id: "po1", label: "payout po_7Kd1", meta: "$2,480.00 to Lindqvist Design AB" },
  { id: "po2", label: "payout po_8Qw3", meta: "$18,600.00 to Harbor Freight Co.", verdicts: { amount: "hold" } },
  {
    id: "po3",
    label: "payout po_2Zc9",
    meta: "$940.00 to a new bank account",
    verdicts: { country: "block" },
    reason: "Bank country isn't on your allowlist",
  },
];

/* ── Bits ── */

function Tick({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
      <path d="M3.5 8.5 6.5 11.5 12.5 5" />
    </svg>
  );
}

function Arrow({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
      <path d="M3.5 8h9M8.5 4l4 4-4 4" />
    </svg>
  );
}

/* ── Section ── */

export function CheckpointFeature() {
  return (
    <section
      style={{ "--accent": ACCENT, "--accent-dark": ACCENT_DARK } as React.CSSProperties}
      className="overflow-x-clip bg-white text-zinc-950 dark:bg-zinc-950 dark:text-white"
    >
      <div className="mx-auto grid max-w-6xl grid-cols-1 items-center gap-12 px-6 py-20 sm:py-24 lg:grid-cols-[minmax(0,26rem)_1fr] lg:gap-16">
        <div>
          <p className="text-[13px] font-medium text-(--accent) dark:text-(--accent-dark)">{EYEBROW}</p>
          <h2 className="mt-2 text-balance text-[2rem] font-medium leading-[1.1] tracking-[-0.035em] sm:text-[2.6rem]">{HEADLINE}</h2>
          <p className="mt-4 text-pretty text-[15px] leading-relaxed text-zinc-500 dark:text-zinc-400">{SUBLINE}</p>
          <ul className="mt-8 space-y-5">
            {POINTS.map((p) => (
              <li key={p.title} className="flex gap-3.5">
                <span className="mt-0.5 grid size-[18px] shrink-0 place-items-center rounded-[5px] bg-(--accent) text-white dark:bg-(--accent-dark)">
                  <Tick className="size-3" />
                </span>
                <span>
                  <span className="block text-[15px] font-medium tracking-tight">{p.title}</span>
                  <span className="mt-1 block text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">{p.body}</span>
                </span>
              </li>
            ))}
          </ul>
          <a
            href={CTA.href}
            className="mt-9 inline-flex items-center gap-1.5 rounded-sm text-sm font-semibold text-(--accent) transition-[gap] duration-150 hover:gap-2.5 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--accent) dark:text-(--accent-dark)"
          >
            {CTA.label}
            <Arrow className="size-4" />
          </a>
        </div>

        <div className="relative">
          <div
            aria-hidden
            className="pointer-events-none absolute -inset-10 bg-[radial-gradient(closest-side,color-mix(in_oklab,var(--accent)_10%,transparent),transparent)]"
          />
          <div className="relative rounded-[28px] border border-zinc-950/[0.08] bg-[color-mix(in_oklab,var(--accent)_3%,white)] p-2 shadow-[0_1px_2px_rgba(0,0,0,0.04),0_24px_60px_-30px_rgba(0,0,0,0.18)] dark:border-white/10 dark:bg-zinc-900 dark:shadow-none">
            <div className="flex items-center justify-between gap-3 px-4 pb-3 pt-3">
              <p className="text-[13px] font-medium tracking-tight">{PANEL.title}</p>
              <p className="font-mono text-[11px] text-zinc-500 dark:text-zinc-400">{PANEL.scope}</p>
            </div>
            <div className="rounded-[22px] border border-zinc-950/[0.06] bg-white p-4 sm:p-5 dark:border-white/[0.06] dark:bg-zinc-950">
              <Checkpoint
                className="[--checkpoint-accent:var(--accent)] dark:[--checkpoint-accent:var(--accent-dark)]"
                rules={RULES}
                requests={REQUESTS}
                approveAfter={APPROVE_AFTER}
                approver={APPROVER}
              />
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}
```
