Odometer Pricing
Three pricing tiers whose prices roll digit by digit, like an odometer, when you switch between monthly and yearly.
Overview
Switch billing and every price changes the way a mechanical counter does: each digit rolls through its column to the new value, the ones place first and the tens a beat later, while digits that are no longer needed fold away. The billing note underneath crossfades to show the yearly total.
The switch has a sliding thumb on a soft spring. The featured tier is set in reverse, with its own badge. Cards rise in on first view, and each tier’s checkmarks draw themselves in sequence.
Prices are plain numbers in props; the currency symbol is configurable. Light and dark (.dark), one CSS file, no animation library. Screen readers get the full price as text, and reduced motion swaps values instantly.
When to use
- Marketing and product pages that need a complete, responsive band of content.
When not to use
- Inside dense app screens, where a smaller component fits better.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/odometer-pricing.jsonnpx shadcn@latest add https://sundaeui.dev/r/odometer-pricing.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/odometer-pricing.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/odometer-pricing.jsonWorks in any React project. Files are written to components/sundae/. Read the source in the stage’s Code view. Pro assets need a personal token. See authenticated installs.
- No dependencies beyond React 18 or later.
- Copy odometer-pricing.tsx and odometer-pricing.css into components/sundae/.
- The CSS file is imported by the component, so no extra configuration is needed.
- Import the component and use it as shown below.
Usage
import { OdometerPricing } from '@/components/sundae/odometer-pricing'
<OdometerPricing
title="Simple pricing that grows with you."
plans={[
{ name: 'Starter', description: '…', monthly: 12, yearly: 9, features: ['3 sources'], cta: { label: 'Start free', href: '/signup' } },
{ name: 'Team', description: '…', monthly: 49, yearly: 39, features: ['Unlimited sources'], cta: { label: 'Start trial', href: '/signup?plan=team' }, featured: true, badge: 'Most teams' },
]}
/>Props
| Prop | Type | Default |
|---|---|---|
title*Section heading. | string | — |
plans*name, description, monthly, yearly (per month), features, cta, featured?, badge? | PricingPlan[] | — |
introSupporting paragraph. | string | — |
currencySymbol shown before prices. | string | '$' |
yearlyNoteLabel beside Yearly. | string | 'Save 20%' |
defaultBillingInitial billing period. | 'monthly' | 'yearly' | 'yearly' |
Accessibility
- The billing switch is a radiogroup of two radio buttons.
- The rolling digits are aria-hidden; each price is also given as text, and the billing note is a polite live region.
- Each tier is an article labelled by its name.
Customization
- Colours are --op-* custom properties on .sd-op; dark values under .dark.
- Change --op-accent for the savings label.
- Odometer timing lives in .sd-op__strip (900ms, 60ms stagger per place).
FAQ
- Is Odometer Pricing free?
- Odometer Pricing is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install Odometer Pricing?
- Run npx shadcn@latest add https://sundaeui.dev/r/odometer-pricing.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Odometer Pricing support keyboard and screen readers?
- The billing switch is a radiogroup of two radio buttons. The rolling digits are aria-hidden; each price is also given as text, and the billing note is a polite live region.
- Does Odometer Pricing respect reduced motion?
- Yes. With prefers-reduced-motion set, decorative motion is removed and state changes are instant.
More like this
All sections →- Pro
Ledger Pricing
Sections · Editorial - Pro
Aperture Hero
Sections · Refined - Pro
Chapter Features
Sections · Refined - Pro
Bento Features
Sections · Understated - Pro
Testimonial Columns
Sections · Refined - Pro
Metric Strip
Sections · Refined