# Plan Fit: Signup Step

> The plan step of a signup flow in a narrow column: the cards become a swipe row and the button names the plan that fits.

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

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/plan-fit-signup-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/plan-fit-signup-demo/light.png
- Registry item: https://keyframeui.com/r/plan-fit-signup-demo.json
- Categories: pricing, interaction, data
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/plan-fit-signup-demo.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/plan-fit.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/plan-fit#plan-fit-signup-demo

## Files

### `registry/demos/plan-fit-signup-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { PlanFit, type PlanFitPlan } from "@/components/ui/plan-fit";

// The plan step of a signup flow, in a narrow column: the cards become a
// swipe row, and the button under it always names the plan that fits.
const PLANS: PlanFitPlan[] = [
  {
    id: "hobby",
    name: "Hobby",
    pitch: "Side projects and weekend experiments.",
    price: 0,
    included: 200,
    caption: "Watermarked",
    features: ["Standard queue", "1024px renders"],
  },
  {
    id: "studio",
    name: "Studio",
    pitch: "For a designer generating every day.",
    price: 18,
    included: 2000,
    overage: { price: 5, per: 500 },
    caption: "Billed monthly",
    features: ["Fast queue", "4K upscales", "Commercial license"],
  },
  {
    id: "team",
    name: "Team",
    pitch: "Shared styles for a whole studio.",
    price: 90,
    included: 15000,
    overage: { price: 20, per: 2500 },
    caption: "Billed monthly",
    features: ["Shared style library", "Ten seats", "Usage by member"],
  },
];

export function PlanFitSignupDemo() {
  const [plan, setPlan] = React.useState("studio");
  const name = PLANS.find((p) => p.id === plan)?.name;

  return (
    <div className="w-full max-w-sm rounded-[1.75rem] bg-white p-6 shadow-[0_0_0_1px_rgb(9_9_11/0.08),0_24px_48px_-24px_rgb(9_9_11/0.25)] dark:bg-zinc-900 dark:shadow-[0_0_0_1px_rgb(255_255_255/0.08)]">
      <p className="text-xs font-medium text-zinc-500 dark:text-zinc-400">Step 2 of 3</p>
      <h3 className="mt-1 text-lg font-semibold tracking-[-0.02em] text-zinc-950 dark:text-white">
        How many images a month?
      </h3>
      <PlanFit
        plans={PLANS}
        unit="images"
        readoutLabel="images generated per month"
        defaultValue={900}
        max={40000}
        onFitChange={setPlan}
        className="mt-6 [--plan-fit-accent-ink:#0a0a0a] [--plan-fit-accent:#ff2e88] [--plan-fit-surface:#ffffff] dark:[--plan-fit-surface:#18181b]"
      />
      <button
        type="button"
        className="mt-6 h-11 w-full rounded-full bg-zinc-950 text-sm font-semibold text-white transition-[background-color,scale] duration-150 active:scale-[0.98] hover:bg-zinc-800 dark:bg-white dark:text-zinc-950 dark:hover:bg-zinc-200"
      >
        Continue with {name}
      </button>
    </div>
  );
}
```
