Componentform1.13 KB gzipZero dependencies
Pricing Slider
Seat slider with live tier cost math.
Preview
Live Preview
Starter
$420
$7/seat/mo, billed yearly
Pro
$1,380
$23/seat/mo, billed yearly
Scale
$4,740
$79/seat/mo, billed yearly
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add pricing-sliderOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { PricingSlider } from "@/components/ui/pricing-slider";
<PricingSlider tiers={[{ name: "Pro", monthly: 29, annual: 23 }]} />Usage guidance
Use Pricing Slider when a ticket stub surface needs Seat slider with live tier cost math. Keep props minimal and prefer composition with sibling stubs.
Avoid Pricing Slider for long form flows or dense data grids where a dedicated layout block fits better.
Source code
pricing-slider.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface PricingTier {
name: string;
monthly: number;
annual: number;
unit?: string;
}
export interface PricingSliderProps extends React.HTMLAttributes<HTMLDivElement> {
tiers: PricingTier[];
seats?: number;
defaultSeats?: number;
maxSeats?: number;
currency?: string;
}
export function PricingSlider({
tiers,
seats: controlledSeats,
defaultSeats = 5,
maxSeats = 100,
currency = "$",
className,
...props
}: PricingSliderProps) {
const [innerSeats, setInnerSeats] = React.useState(defaultSeats);
const seats = controlledSeats ?? innerSeats;
const [annual, setAnnual] = React.useState(true);
if (tiers.length === 0) {
return <div className={cn("w-full", className)} {...props} />;
}
return (
<div className={cn("w-full", className)} {...props}>
<div className="flex items-center justify-between gap-3">
<label className="font-mono text-xs text-muted-foreground">
Seats: <strong className="text-foreground">{seats}</strong>
</label>
<div className="flex rounded-md border border-border p-0.5 font-mono text-[11px]" role="group" aria-label="Billing period">
{(["Monthly", "Annual"] as const).map((p) => {
const isAnnual = p === "Annual";
const active = annual === isAnnual;
return (
<button
key={p}
type="button"
aria-pressed={active}
onClick={() => setAnnual(isAnnual)}
className={cn(
"cursor-pointer rounded px-3 py-1 uppercase transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
active ? "bg-primary font-bold text-primary-foreground" : "text-muted-foreground hover:text-foreground",
)}
>
{p}
</button>
);
})}
</div>
</div>
<input
type="range"
min={1}
max={maxSeats}
value={seats}
onChange={(e) => setInnerSeats(Number(e.target.value))}
aria-label="Seats"
className="mt-3 w-full accent-accent"
/>
<div className="mt-4 grid gap-2 sm:grid-cols-3">
{tiers.map((tier) => {
const perSeat = annual ? tier.annual : tier.monthly;
const total = perSeat * seats * (annual ? 12 : 1);
return (
<div key={tier.name} className="rounded-lg border border-border bg-card p-4">
<p className="font-mono text-[11px] uppercase tracking-widest text-muted-foreground">{tier.name}</p>
<p className="mt-1 font-mono text-2xl font-bold tabular-nums">
{currency}
{total.toLocaleString()}
</p>
<p className="mt-0.5 font-mono text-[11px] text-muted-foreground">
{currency}
{perSeat}/seat/mo{annual ? ", billed yearly" : ""}
</p>
</div>
);
})}
</div>
</div>
);
}
Props
| Prop | Type | Description |
|---|---|---|
| tiers | PricingTier[] | Pricing tiers (required). |
| seats | number | Controlled seat count. |
| maxSeats | number | Slider max (default 100). |
| currency | string | Currency symbol (default '$'). |