# Pricing 01

> Three-tier card row with a monthly/yearly toggle that swaps each price and billing note, a featured middle plan, feature checklist and CTA.

A shadcn/ui block from the [@hirael](https://hirael.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/hirael/pricing-01).

- Page, with a live preview: https://shadcn.exchange/r/hirael/pricing-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/hirael/pricing-01-light-698bffed5a8c64ca.png
- Registry item: https://hirael.com/r/pricing-01.json
- Categories: blocks, pricing
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://hirael.com/r/pricing-01.json
```

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

## Dependencies

- npm packages: `lucide-react`
- Registry items: `button`, `card`, `separator`, `toggle-group`

## Files

### `registry/hirael/bases/radix/blocks/pricing-01/pricing-01.tsx`

```tsx
// Pricing 01 from Hirael <https://hirael.com/blocks/pricing/pricing-01>
// MIT · Mohammad Shehadeh · https://github.com/MohammadShehadeh/hirael

'use client';

import * as React from 'react';
import { Check } from 'lucide-react';

import { Button } from '@/registry/hirael/bases/radix/ui/button';
import { Card, CardContent, CardFooter, CardHeader } from '@/registry/hirael/bases/radix/ui/card';
import { Separator } from '@/registry/hirael/bases/radix/ui/separator';
import { ToggleGroup, ToggleGroupItem } from '@/registry/hirael/bases/radix/ui/toggle-group';
import { cn } from '@/lib/utils';

const ENTER =
  'animate-in fade-in slide-in-from-bottom-4 duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] fill-mode-both motion-reduce:animate-none';
const SWAP =
  'animate-in fade-in slide-in-from-bottom-1 duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] fill-mode-both motion-reduce:animate-none';

const stagger = (index: number, step = 60, offset = 0): React.CSSProperties => ({
  animationDelay: `${offset + index * step}ms`,
});

type Billing = 'monthly' | 'yearly';

const PAID_MONTHS_PER_YEAR = 10;

interface Tier {
  name: string;
  /** Monthly price in whole dollars when billed monthly. */
  monthly: number;
  blurb: string;
  features: readonly string[];
  cta: string;
  featured?: boolean;
  ctaVariant: 'default' | 'outline';
}

const TIERS: readonly Tier[] = [
  {
    name: 'Solo',
    monthly: 0,
    blurb: 'For freelancers sending a handful of invoices a month.',
    features: ['5 active clients', 'Invoices and quotes', 'Card and bank payments', 'Email reminders'],
    cta: 'Start free',
    ctaVariant: 'outline',
  },
  {
    name: 'Studio',
    monthly: 24,
    blurb: 'For small studios billing retainers and project work.',
    features: ['Unlimited clients', 'Recurring invoices', 'Client portal with your logo', 'Time tracking'],
    cta: 'Try Studio free for 14 days',
    featured: true,
    ctaVariant: 'default',
  },
  {
    name: 'Agency',
    monthly: 64,
    blurb: 'For agencies with several teams and a finance lead.',
    features: ['Everything in Studio', 'Up to 25 team members', 'Approval before sending', 'Accounting sync'],
    cta: 'Talk to us',
    ctaVariant: 'outline',
  },
];

const money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });

const priceFor = (tier: Tier, billing: Billing) =>
  billing === 'yearly' ? Math.round((tier.monthly * PAID_MONTHS_PER_YEAR) / 12) : tier.monthly;

const Pricing01 = () => {
  const [billing, setBilling] = React.useState<Billing>('monthly');

  return (
    <section data-slot="pricing" className="bg-background py-20 sm:py-28">
      <div className="mx-auto w-full max-w-[1480px] px-4">
        <div data-slot="pricing-header" className="mx-auto flex max-w-2xl flex-col items-center gap-5 text-center">
          <span className={cn(ENTER, 'text-xs text-muted-foreground uppercase')}>Pricing</span>
          <h2
            style={stagger(1)}
            className={cn(ENTER, 'font-serif text-4xl leading-[1.04] font-medium tracking-tight sm:text-5xl')}
          >
            Send invoices free. Pay when you <span className="text-foreground italic">grow</span>.
          </h2>
          <p style={stagger(2)} className={cn(ENTER, 'text-base text-muted-foreground sm:text-lg')}>
            Every plan takes card and bank payments with no extra fee from us. Upgrade when you need more clients or
            more people.
          </p>
          <div style={stagger(3)} className={cn(ENTER, 'mt-2 flex flex-col items-center gap-2')}>
            <ToggleGroup
              data-slot="pricing-billing"
              type="single"
              variant="outline"
              value={billing}
              onValueChange={(next) => next && setBilling(next as Billing)}
              aria-label="Billing period"
            >
              <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
              <ToggleGroupItem value="yearly">Yearly</ToggleGroupItem>
            </ToggleGroup>
            <span className="text-xs text-muted-foreground">Yearly plans get 2 months free</span>
          </div>
        </div>

        <div data-slot="pricing-tiers" className="mt-14 grid grid-cols-1 gap-4 md:grid-cols-3">
          {TIERS.map((tier, index) => {
            const price = priceFor(tier, billing);

            return (
              <Card
                key={tier.name}
                data-slot="pricing-tier"
                data-featured={tier.featured ? '' : undefined}
                style={{
                  ...stagger(index, 60, 240),
                  ...(tier.featured
                    ? {
                        backgroundImage:
                          'radial-gradient(120% 90% at 50% 0%, color-mix(in oklch, var(--primary) 9%, transparent), transparent 60%)',
                      }
                    : undefined),
                }}
                className={cn(ENTER, 'relative')}
              >
                {tier.featured && (
                  <span className="absolute end-6 -top-2.5 inline-flex items-center rounded-full border border-border bg-background px-2.5 py-0.5 text-xs text-foreground uppercase">
                    Most popular
                  </span>
                )}

                <CardHeader className="flex flex-col">
                  <h3 className="text-xs font-normal text-muted-foreground uppercase">{tier.name}</h3>
                  <div key={billing} data-slot="pricing-price" className={cn(SWAP, 'flex flex-col gap-1')}>
                    <div className="flex items-baseline gap-1">
                      <span dir="ltr" className="text-4xl font-semibold tracking-[-0.04em] tabular-nums">
                        {money.format(price)}
                      </span>
                      <span className="text-xs text-muted-foreground">/ month</span>
                    </div>
                    <span className="text-xs text-muted-foreground">
                      {tier.monthly === 0 ? (
                        'Free for as long as you like'
                      ) : billing === 'yearly' ? (
                        <>
                          <span dir="ltr" className="tabular-nums">
                            {money.format(tier.monthly * PAID_MONTHS_PER_YEAR)}
                          </span>{' '}
                          billed once a year
                        </>
                      ) : (
                        'Billed monthly, cancel any time'
                      )}
                    </span>
                  </div>
                  <p className="pt-1 text-sm text-muted-foreground">{tier.blurb}</p>
                </CardHeader>

                <Separator />

                <CardContent>
                  <ul className="flex flex-col gap-2.5">
                    {tier.features.map((feature) => (
                      <li key={feature} className="flex items-start gap-2.5 text-sm text-foreground">
                        <Check aria-hidden className="mt-0.5 size-4 shrink-0 text-foreground" />
                        {feature}
                      </li>
                    ))}
                  </ul>
                </CardContent>

                <CardFooter className="mt-auto">
                  <Button asChild variant={tier.ctaVariant} size="lg" className="w-full">
                    <a href="#">{tier.cta}</a>
                  </Button>
                </CardFooter>
              </Card>
            );
          })}
        </div>
      </div>
    </section>
  );
};

export default Pricing01;
```
