Skip to content
txbrendan/ui

Call To Action

beta

A next step offered in a card: a scene that shows it, what it is, why, how far along you are, and the step itself.

Preview

Invite your team

They see the plan and pick up work, without asking you.

3 of 4 done

Take payment with bookings

People pay when they book a spot, so nobody has to chase them.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/call-to-action

Use it for

  • Where the space would sit empty otherwise: a workspace still setting up, a feature nobody has tried yet.
  • title says what the step is. text says why, in a line or two. Put a Scene in scene to show it.
  • main is the step itself, a primary button with its icon. later is the quiet way out: "Skip" or "Later".
  • Pass progress, like { done: 3, of: 4 }, when the step is one of a set.
  • Give main a result, like { label: "Invites sent", onUndo, onCommit }, to show what the press did on the button, with Undo for 8 seconds.

Avoid it for

  • More than one on a screen.
  • Warnings or news. Use an alert.
  • Steps people can't skip. Put them in the page's flow.

Props

  • title

    Required. What the step is: "Invite your team". It names the card for screen readers.

    string

  • text

    Why, in a line or two.

    ReactNode

  • scene

    A Scene, inset 6px from the card's edge.

    ReactNode

  • progress

    How far along a set of steps is: a small ring and "3 of 4".

    { done: number; of: number }

  • main

    Required. The step itself: a brand button with its icon. icon is a name from Icon's set or any icon. A returned promise shows it's busy.

    { label: string; icon?: IconName | ReactNode; onClick?: () => void | Promise<unknown>; result?: ActionResult; loading?: boolean; disabled?: boolean }

  • later

    The quiet way out, a ghost button before the step.

    { label: string; onClick?: () => void }

  • className

    Extra classes on the card. Later classes win.

    string

Accessibility

  • The card is a region named by its title.
  • Tab goes to the way out, then the step. Both are buttons.
  • Progress reads as "3 of 4 done". The ring is hidden, since the words say it.
  • A result changes the step's own name, like "Invites sent. Press to undo.", and is said in a polite live region.
  • Undo only undoes once 1.2 seconds have passed, so a double press never undoes. ⌘Z takes it back too.

Examples

With progress, a way out, and Undo

import { UserPlus } from "lucide-react";

<CallToAction
  scene={
    <Scene>
      <SceneHint icon={<UserPlus />} label="Invite people" />
    </Scene>
  }
  title="Invite your team"
  text="They see the plan and pick up work, without asking you."
  progress={{ done: 3, of: 4 }}
  later={{ label: "Skip", onClick: () => console.log("skip") }}
  main={{
    label: "Invite people",
    icon: <UserPlus />,
    result: { label: "Invites sent", onUndo: () => {}, onCommit: () => console.log("send invites") },
  }}
/>