Seat Checkout
A complete SaaS checkout with plans, billing interval, seats, add-ons, tax, promo codes and a live order summary.
Overview
A real subscription checkout, not a card form on its own. Choose a plan, switch between monthly and yearly, and adjust seats with a stepper that respects each plan’s minimum. Add optional add-ons, enter billing details and pick card, bank debit or invoice. The summary recalculates every line as you go: seats × interval, add-ons, promo discount, and tax by country, which is waived when a tax ID makes it reverse-charge.
Validation runs on submit. It checks the card number with Luhn, rejects past expiry dates, and checks CVC length, bank account format and email. Errors are linked to their fields. The pay button reflects the method and the exact total, shows a processing state, and ends in a confirmation screen or an inline error.
Use it for B2B SaaS upgrades and team plans. When you have one price and nothing to configure, use a simple pricing table instead. It is the interface only: tokenize card data with your payment provider and re-validate prices and promo codes on the server.
When to use
- Payment and purchase steps that need trust and clarity.
When not to use
- Taking real payments without a PCI-compliant processor behind it.
Installation
pnpm dlx shadcn@latest add https://sundaeui.dev/r/seat-checkout.jsonnpx shadcn@latest add https://sundaeui.dev/r/seat-checkout.jsonyarn dlx shadcn@latest add https://sundaeui.dev/r/seat-checkout.jsonbunx --bun shadcn@latest add https://sundaeui.dev/r/seat-checkout.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 seat-checkout.tsx into components/sundae/.
- Make sure Tailwind scans that folder (v4 detects it automatically; v3 needs it in `content`).
- Import the component and use it as shown below.
Usage
import { SeatCheckout } from '@/components/sundae/seat-checkout'
<SeatCheckout
plans={[
{ id: 'team', name: 'Team', blurb: 'Shared workspaces', perSeat: { monthly: 1800, yearly: 1440 }, minSeats: 3, features: [] },
]}
addons={[{ id: 'support', name: 'Priority support', description: '4-hour response', monthly: 4900 }]}
taxRates={{ GB: 0.2 }}
countries={[{ code: 'US', name: 'United States' }, { code: 'GB', name: 'United Kingdom' }]}
onSubmit={async (order) => {
const res = await fetch('/api/subscribe', { method: 'POST', body: JSON.stringify(order) })
if (!res.ok) throw new Error('Payment failed. Try another method.')
}}
/>Props
| Prop | Type | Default |
|---|---|---|
plans*{ id, name, blurb, perSeat: { monthly, yearly } (cents), minSeats?, features }. | CheckoutPlan[] | — |
addons{ id, name, description, monthly (cents) }. Yearly is charged 12×. | CheckoutAddon[] | — |
taxRatesRate by country code. A tax ID switches to reverse charge (0%). | Record<string, number> | — |
countriesCountry options for billing. | { code, name }[] | — |
promoCodesClient-side preview of discounts. Always re-validate on the server. | Record<string, number> | — |
currencyISO currency code. | string | 'USD' |
onSubmit*Receives the full order, including the billing address. Throw to show an error. | (order: CheckoutResult) => Promise<void> | — |
expressMethodsWallet buttons shown above the card form, e.g. Apple Pay or Google Pay. Omit to hide the row. | { id, label }[] | — |
onExpressPayCalled when an express button is pressed. Throw to show an error. | (methodId, order) => Promise<void> | — |
testModeShows a test-mode badge, a simulated-payment note and a “Use test card” helper. | boolean | — |
Accessibility
- Plans, interval and payment method are radio groups with aria-checked. The seat stepper has labelled buttons and an editable input.
- Every field has a label. Errors are tied to their field with aria-describedby and aria-invalid.
- The summary and total are polite live regions, so changes are announced.
Customization
- It uses Tailwind’s zinc scale with dark: variants. Swap the zinc and emerald classes for your palette.
- Replace the card inputs with your provider’s hosted fields and keep the layout and summary logic.
FAQ
- Is Seat Checkout free?
- Seat Checkout is part of Sundae Pro. The live preview is open to everyone; the source installs with a Pro account.
- How do I install Seat Checkout?
- Run npx shadcn@latest add https://sundaeui.dev/r/seat-checkout.json. The files are written to components/sundae/. It has no dependencies beyond React 18 or later.
- Does Seat Checkout support keyboard and screen readers?
- Plans, interval and payment method are radio groups with aria-checked. The seat stepper has labelled buttons and an editable input. Every field has a label. Errors are tied to their field with aria-describedby and aria-invalid.
- Does Seat Checkout respect reduced motion?
- Yes. With prefers-reduced-motion set, decorative motion is removed and state changes are instant.
More like this
All checkout →- Pro
Tender Checkout
Checkout · Refined - Pro
Team Members
Components · Understated - Pro
Onboarding Flow
Forms · Refined - Pro
Billing Toggle
Checkout · Refined - Pro
Seat Pricing
Checkout · Refined - Pro
Payment Methods
Checkout · Refined