Plan Fit

Usage-Based Pricing Slider for React

A pricing grid with a usage slider over it, cut into one stretch per plan. Drag it and every card measures your usage against its allowance: past the limit the overage shows as a hatched stretch and that card's price climbs a pack at a time. The highlight glides to whichever plan is cheapest at that usage, so a smaller plan with overage can keep it until the next one costs less.

  • Pricing
  • Interaction
  • Data

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude
glideDuration0.42s
glideBounce0.12
countDuration0.35s
<PlanFit />

The demo follows as you drag.

60

hours of audio transcribed per month

Free

Try it on your own recordings.

$0/mo

No card needed

Tops out at 5

Start free

5 hours

  • English and Spanish
  • Speaker labels
  • Community forum
Build (Best fit)

For apps shipping their first voice feature.

$29/mo

Billed monthly

60 of 100 hours

Start building

100 hours

  • 40 languages
  • Word-level timestamps
  • Email support
Scale

For products people talk to every day.

$249/mo

Billed monthly

60 of 1K hours

Choose Scale

1,000 hours

  • Live streaming
  • Custom vocabulary
  • 99.9% uptime SLA
Enterprise

Dedicated capacity in your own region.

Custom

Volume pricing

Any volume

Talk to sales

Any number of hours

  • On-prem or your VPC
  • A named engineer
  • Signed DPA and SOC 2

Best fit: Build

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Plan Fit: A pricing grid with a usage slider over it, cut into one stretch per plan. Drag it and every card measures your usage against its allowance: past the limit the overage shows as a hatched stretch and that card's price climbs a pack at a time. The highlight glides to whichever plan is cheapest at that usage, so a smaller plan with overage can keep it until the next one costs less.
This demo, Usage-Based API: A speech-to-text API billed by audio hours, with yearly billing. Build sells extra hours in packs, so it stays the best fit well past its 100 hours, until Scale costs less.

1. Install it:
   npx shadcn@latest add @keyframeui/plan-fit-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/plan-fit-demo.json instead.)
   It adds the demo, components/plan-fit-demo.tsx, and the component, components/ui/plan-fit.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <PlanFit> on its own.

3. These props set its feel. Change them before you change the file:
   - glideDuration (default 0.42): How long the highlight takes to glide over to a new best plan, in seconds (its spring's visual duration).
   - glideBounce (default 0.12): How far the highlight overshoots as it lands on a new plan, from 0 (not at all) to 1.
   - countDuration (default 0.35): How long every price takes to count to its new figure when the billing option changes, in seconds.

4. Don't change, in components/ui/plan-fit.tsx:
   - Usage, prices, meters and the readout are motion values derived from one `pos`, written into text nodes, `transform`s and the slider's ARIA values, so dragging never re-renders a card.
   - The highlight is one layer under the cards that glides by `transform` and turns from wherever it is; a card's accents arrive only as it lands and leave first, so two cards are never lit.
   - Pressing the ball never moves it: the drag keeps the offset it was taken hold at. A finger drags only once it moves sideways; a vertical move scrolls the page.
   - Reduced motion re-keys the highlight so it fades in at its new card; jumps, prices and the swipe row land at once, and dragging still moves everything 1:1.
   - The thumb is the `role="slider"` keyboard target, and a new fit is announced through the polite live region.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/plan-fit.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/plan-fit.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/plan-fit

Examples

Finished sections built with Plan Fit. Each one installs whole, with the component included.

Capped Tiers

A newsletter tool priced by list size, where each plan stops at its cap and the next one takes over.

2,400

readers on your list

Starter

Your first issue, and the hundred after it.

$0/mo

Free forever

Tops out at 1K

Start writing

1,000 subscribers

  • Unlimited sends
  • Signup forms
  • Tinta badge in the footer
Creator (Your plan)

For writers turning readers into a business.

$15/mo

Billed monthly

2.4K of 10K subscribers

Go Creator

10,000 subscribers

  • Paid subscriptions
  • Your own domain
  • Referral rewards
Publisher

For a masthead with editors and sponsors.

$59/mo

Billed monthly

2.4K of 50K subscribers

Go Publisher

50,000 subscribers

  • Five editor seats
  • Sponsor slots
  • Priority sending

Your plan: Creator

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Plan Fit: A pricing grid with a usage slider over it, cut into one stretch per plan. Drag it and every card measures your usage against its allowance: past the limit the overage shows as a hatched stretch and that card's price climbs a pack at a time. The highlight glides to whichever plan is cheapest at that usage, so a smaller plan with overage can keep it until the next one costs less.
This demo, Capped Tiers: A newsletter tool priced by list size, where each plan stops at its cap and the next one takes over.

1. Install it:
   npx shadcn@latest add @keyframeui/plan-fit-audience-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/plan-fit-audience-demo.json instead.)
   It adds the demo, components/plan-fit-audience-demo.tsx, and the component, components/ui/plan-fit.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <PlanFit> on its own.

3. These props set its feel. Change them before you change the file:
   - glideDuration (default 0.42): How long the highlight takes to glide over to a new best plan, in seconds (its spring's visual duration).
   - glideBounce (default 0.12): How far the highlight overshoots as it lands on a new plan, from 0 (not at all) to 1.
   - countDuration (default 0.35): How long every price takes to count to its new figure when the billing option changes, in seconds.

4. Don't change, in components/ui/plan-fit.tsx:
   - Usage, prices, meters and the readout are motion values derived from one `pos`, written into text nodes, `transform`s and the slider's ARIA values, so dragging never re-renders a card.
   - The highlight is one layer under the cards that glides by `transform` and turns from wherever it is; a card's accents arrive only as it lands and leave first, so two cards are never lit.
   - Pressing the ball never moves it: the drag keeps the offset it was taken hold at. A finger drags only once it moves sideways; a vertical move scrolls the page.
   - Reduced motion re-keys the highlight so it fades in at its new card; jumps, prices and the swipe row land at once, and dragging still moves everything 1:1.
   - The thumb is the `role="slider"` keyboard target, and a new fit is announced through the polite live region.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/plan-fit.md

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.

Step 2 of 3

How many images a month?

900

images generated per month

Hobby

Side projects and weekend experiments.

$0/mo

Watermarked

Tops out at 200

200 images

  • Standard queue
  • 1024px renders
Studio (Best fit)

For a designer generating every day.

$18/mo

Billed monthly

900 of 2K images

2,000 images

  • Fast queue
  • 4K upscales
  • Commercial license
Team

Shared styles for a whole studio.

$90/mo

Billed monthly

900 of 15K images

15,000 images

  • Shared style library
  • Ten seats
  • Usage by member

Best fit: Studio

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Plan Fit: A pricing grid with a usage slider over it, cut into one stretch per plan. Drag it and every card measures your usage against its allowance: past the limit the overage shows as a hatched stretch and that card's price climbs a pack at a time. The highlight glides to whichever plan is cheapest at that usage, so a smaller plan with overage can keep it until the next one costs less.
This demo, 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.

1. Install it:
   npx shadcn@latest add @keyframeui/plan-fit-signup-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/plan-fit-signup-demo.json instead.)
   It adds the demo, components/plan-fit-signup-demo.tsx, and the component, components/ui/plan-fit.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <PlanFit> on its own.

3. These props set its feel. Change them before you change the file:
   - glideDuration (default 0.42): How long the highlight takes to glide over to a new best plan, in seconds (its spring's visual duration).
   - glideBounce (default 0.12): How far the highlight overshoots as it lands on a new plan, from 0 (not at all) to 1.
   - countDuration (default 0.35): How long every price takes to count to its new figure when the billing option changes, in seconds.

4. Don't change, in components/ui/plan-fit.tsx:
   - Usage, prices, meters and the readout are motion values derived from one `pos`, written into text nodes, `transform`s and the slider's ARIA values, so dragging never re-renders a card.
   - The highlight is one layer under the cards that glides by `transform` and turns from wherever it is; a card's accents arrive only as it lands and leave first, so two cards are never lit.
   - Pressing the ball never moves it: the drag keeps the offset it was taken hold at. A finger drags only once it moves sideways; a vertical move scrolls the page.
   - Reduced motion re-keys the highlight so it fades in at its new card; jumps, prices and the swipe row land at once, and dragging still moves everything 1:1.
   - The thumb is the `role="slider"` keyboard target, and a new fit is announced through the polite live region.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/plan-fit.md

Props

plans Required

TypePlanFitPlan[]

The plans, smallest allowance first, as { id, name, price, included?, overage?, limit?, pitch?, caption?, features?, cta? }. price is a number, one per billing option ({ monthly: 29, yearly: 23 }), or text like "Custom" for a quoted plan. overage: { price, per } sells extra usage in packs; without it a plan stops at included. Each plan gets a card and a stretch of the rail; up to five fit in a row.

unit

TypestringDefault"credits"

What usage is counted in, plural: "credits", "minutes". Used in the readout, the captions and the slider's name.

readoutLabel

TypeReactNode

The line under the big number. Defaults to <unit> per month.

defaultValue

Typenumber

The usage the thumb starts at. Defaults to a point 60% along the second plan's stretch.

min

TypenumberDefault0

The usage at the rail's left end.

max

Typenumber

The usage at the rail's right end. Defaults to the last plan's allowance, or four times the largest one when the last plan is quoted.

billing

TypePlanFitBilling[]

Options for a billing switch beside the readout, as { id, label, note? }, e.g. { id: "yearly", label: "Yearly", note: "−20%" }. Switching counts every price to its new figure. Leave it out for one price per plan.

defaultBilling

Typestring

The billing option selected at first. Defaults to the first.

currency

TypestringDefault"USD"

An ISO currency code for every price.

locale

TypestringDefault"en-US"

The locale numbers and prices are formatted in.

period

TypestringDefault"/mo"

Shown after each price.

fitLabel

TypestringDefault"Best fit"

The tag on the highlighted plan, also read out to screen readers.

onValueChange

Type(usage: number) => void

Called with the usage whenever it changes.

onFitChange

Type(planId: string) => void

Called with the best plan's id once on mount, then whenever a different plan becomes the best fit.

glideDuration

TypenumberDefault0.42

How long the highlight takes to glide over to a new best plan, in seconds (its spring's visual duration).

glideBounce

TypenumberDefault0.12

How far the highlight overshoots as it lands on a new plan, from 0 (not at all) to 1.

countDuration

TypenumberDefault0.35

How long every price takes to count to its new figure when the billing option changes, in seconds.

className

Typestring

Extra classes for the root. Theme it with CSS variables: --plan-fit-accent (default #ff4f12), --plan-fit-accent-ink for text on the accent, --plan-fit-accent-text for accent-colored words, and --plan-fit-surface for the cards' fill.