Commerce / React + CSS
Pricing card
A billing toggle updates a crisp pricing summary and its next action.
$32 billed monthly
- Shared projects
- Component collections
- Team feedback
"use client";
import styles from "./PricingCard.module.css";
import { useState } from "react";
export default function PricingCard({
name = "Studio",
monthly = 32,
annualMonthly = 26,
onChoose,
}) {
const [annual, setAnnual] = useState(false);
const [selected, setSelected] = useState(false);
return (
<div className={styles.root}>
<div className={styles.head}>
<span>{name}</span>
<span className={styles.badge}>For small teams</span>
</div>
<button
type="button"
className={styles.billing}
role="switch"
aria-checked={annual}
onClick={() => {
setAnnual(!annual);
setSelected(false);
}}
>
Annual billing{" "}
<span aria-hidden="true">
<i />
</span>
</button>
<div className={styles.price} key={String(annual)}>
<strong>${annual ? annualMonthly : monthly}</strong>
<span>/ month</span>
</div>
<p>
{annual
? `$${annualMonthly * 12} billed yearly`
: `$${monthly} billed monthly`}
</p>
<ul>
<li>Shared projects</li>
<li>Component collections</li>
<li>Team feedback</li>
</ul>
<button
type="button"
className={styles.choose}
onClick={() => {
setSelected(true);
onChoose?.({
name,
annual,
amount: annual ? annualMonthly * 12 : monthly,
});
}}
>
Choose {name}
<span aria-hidden="true">↗</span>
</button>
<small role="status">
{selected
? "Plan selected. Demo only; no purchase made."
: "Illustrative pricing · no checkout"}
</small>
</div>
);
}
Make it yours
Use to compare billing choices with an explicit total. The example pricing is illustrative, not TDP pricing. Connect onChoose to a real checkout only after validating prices server-side.
- Use a React project with CSS Modules support, such as Next.js or Vite.
- Save the JSX as
PricingCard.jsxand the CSS asPricingCard.module.cssin the same folder. - Import
PricingCardand connect its props to your application. Controlled components need parent state and change handlers.
No third-party component or animation package is required. Use the theme controls to adjust accent, corners, spacing, and type. Copy the Theme CSS into your stylesheet and wrap your components in <div className="tdp-components">. The theme applies to every component inside that wrapper. TDP defaults work without a theme file.
Props
| Prop | Type | Use |
|---|---|---|
name | string | Plan label. |
monthly | number | Monthly billed price in USD. |
annualMonthly | number | Monthly equivalent for an annual plan. |
onChoose | function | Receives the selected plan and billing amount. |
Accessibility & behavior
Annual billing is a named switch. The yearly total stays visible; the button does not imply a completed purchase. Price movement respects reduced motion.
Visible focus styles and reduced-motion support are included. Check labels, contrast, keyboard behavior, and screen-reader output again after adapting the component to your product.