apx-dsv0.1Local renderer · live
K
Foundations4
  • Getting started
  • Theming
  • Templates
  • Icons
  • Avatar
  • Badge
  • DataGrid
  • Scheduler
  • Stat
  • Table
  • Timeline
  • TreeView
  • Accordion
  • Alert
  • EmptyState
  • Progress
  • Skeleton
  • Spinner
  • SplashScreen
  • Toast
  • ColorPicker
  • FileUpload
  • Form
  • Rating
  • TagsInput
  • Combobox
  • Field
  • Select
  • Toggle
  • Button
  • Calendar
  • Checkbox
  • DatePicker
  • Input
  • NumberInput
  • Radio
  • Slider
  • Switch
  • Textarea
  • AppShell
  • Div
  • Divider
  • Sidebar
  • Stack
  • Typography
  • Image
  • Breadcrumbs
  • Carousel
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Toolbar
  • CommandPalette
  • Confirm
  • Drawer
  • HoverCard
  • Menu
  • Modal
  • Popover
  • Tooltip
  • Icon
  • Card
  • PricingCard
60 componentsapx-ds/renderer
PricingCard
Variant↳ other

Surfaces

PricingCard

Opinionated pricing tier card. Wraps

PricingCard

<PricingCard /> is a higher-level composition primitive for SaaS pricing tiers. It wraps <Card> + its subparts, then exposes the recurring decisions (name, price, cadence, blurb, features, CTA, "most popular" highlight) as typed props — so a tier collapses from ~60 LoC of structural JSX into one declarative element.

tsx
import { Button, PricingCard } from 'apx-ds';

<PricingCard
  highlighted
  name="Team"
  price="$12"
  cadence="per seat / month, billed yearly"
  blurb="For growing teams that need richer collaboration & analytics."
  features={[
    'Everything in Starter',
    'Unlimited collaborators',
    'Advanced analytics dashboards',
    '90-day audit log + SSO',
    'Priority email support',
  ]}
  cta={<Button fullWidth>Start 14-day trial</Button>}
/>
import { Button, PricingCard } from 'apx-ds';

<PricingCard
  highlighted
  name="Team"
  price="$12"
  cadence="per seat / month, billed yearly"
  blurb="For growing teams that need richer collaboration & analytics."
  features={[
    'Everything in Starter',
    'Unlimited collaborators',
    'Advanced analytics dashboards',
    '90-day audit log + SSO',
    'Priority email support',
  ]}
  cta={<Button fullWidth>Start 14-day trial</Button>}
/>

The component renders a regular <Card> underneath — every Card prop you'd reach for (variant, size, color, sx, className, style, data-*, ...) flows through unchanged. There is no new visual primitive to learn; only a typed shorthand.

Why a separate primitive (and not <Card variant="pricing">)?

Card.variant already means edge / background treatment ('outline' | 'solid' | 'elevated' | 'ghost'). Overloading it with a content-layout meaning ('pricing') would conflate two orthogonal concerns and force the type to drift. Following the same pattern as <Stat>, <EmptyState>, <Toolbar>, this DS prefers a dedicated component that wraps Card — you keep every Card escape hatch, and the pricing shape is reusable in its own right.

Anatomy

RegionSource
Headername + (when highlighted) the <Badge>
Price blockprice + optional cadence caption
Blurbblurb paragraph
Feature listfeatures[] rendered with featureIcon
Footercta (any ReactNode, typically <Button>)

Every region renders only when its prop is supplied — pass cadence={undefined} / features={undefined} to omit a section.

Prop surface

Content

PropTypeNotes
nameReactNode (required)Tier label. Bolded next to the optional highlight badge.
priceReactNode (required)Headline price (e.g. '$0', '$12', 'Custom'). Rendered display-size.
cadenceReactNodeCaption below the price ('per seat / month').
blurbReactNodeDescription paragraph between price and features.
featuresreadonly ReactNode[]Feature list; each item rendered as a row with a bullet icon. Strings are auto-wrapped in <Typography> — pass any node for richer rows.
ctaReactNodeAction element (typically a <Button>). Sits in <Card.Footer>.

Highlight

PropTypeDefaultNotes
highlightedbooleanfalseSwitches the canonical recommended-tier visual: variant='elevated', selected={true}, badge next to the name.
highlightLabelReactNode'Most popular'Badge text. Pass null to keep the elevated/selected ring without the badge.
colorCardColor'primary'Palette role used for the badge tint + selection ring.

Feature bullets

PropTypeDefaultNotes
featureIconReactNode<Check />Lucide checkmark by default. Pass any icon to swap.
featureIconColorCardColor'success'Palette role used to tint the bullet icon.

Pass-through to <Card>

PropTypeDefaultNotes
variantCardVariant'elevated' when highlighted, else 'outline'Override the underlying Card's edge/background treatment.
sizeCardSize'lg'Padding density propagated via CardContext to every subpart.
sxSx—Theme-aware inline style on the Card root.
className / style / data-* / aria-*——Forwarded to the underlying <Card> root.

Examples

Basic — single tier

Loading preview…
Basic.tsx

Highlighted — recommended-tier visual

Loading preview…
Highlighted.tsx

TierGrid — three tiers in a responsive grid

Loading preview…
TierGrid.tsx

Accessibility

  • The badge next to the highlighted tier is purely visual; the tier name is the semantic label, so screen reader users hear "Team" (not "Team Most popular"). Pair with an aria-describedby to announce the highlight if needed.
  • featureIcon is wrapped with aria-hidden="true" — the bullet decoration must not be read aloud, the feature text is the accessible content.
  • The component never traps focus or claims a role; the <Card> underneath stays a passive container. If you need the entire card to be a single clickable target, forward clickable via the underlying Card's prop surface (PR follow-up).

Theming

  • All visual styling is owned by <Card> + <Badge> + <Typography> + <Button> recipes — there's no <PricingCard> recipe of its own. Theme overrides on those three components automatically apply.
  • featureIcon color uses the standard palette tokens (text-primary, text-success, ...) so it tracks the active variant.

Do / Don't

  • Do reach for <PricingCard> whenever you'd otherwise wire <Card> + <Card.Header>
    • price block + feature list + <Card.Footer> by hand for a marketing page.
  • Do keep tier data in a typed array and .map() over it; the prop surface is designed for that pattern (see the TierGrid example).
  • Don't force <PricingCard> into roles outside pricing (status cards, summary panels, etc.) — fall back to <Card> directly. The shape is opinionated on purpose.
  • Don't override every prop with className arbitrary utility classes. If the component doesn't fit, drop down to <Card> directly — that path is supported and documented.

Props

PropTypeDefaultDescription
name*ReactNode—Tier name (e.g. `"Starter"`, `"Team"`, `"Enterprise"`). Renders bold next to the optional highlight badge inside the card header.
price*ReactNode—Headline price (e.g. `"$0"`, `"$12"`, `"Custom"`). Rendered display-size so it dominates the card hierarchy.
blurbReactNode—Blurb / one-line description between the price and the feature list. Optional — omit on the cheapest tier where the features speak for themselves.
cadenceReactNode—Cadence text below the price (e.g. `"per seat / month, billed yearly"`, `"forever, up to 5 seats"`). Optional — short prices like `"Custom"` may not need one.
colorenum'primary'Palette role used for the highlight badge + selection ring + (default) feature bullet color.
ctaReactNode—Call-to-action element. Typically a `<Button>` so consumers retain control over variant / color / size, but any `ReactNode` is accepted. Sits inside the card footer at full inline width.
featureIconReactNode—Per-feature bullet icon. Defaults to a checkmark from `lucide-react`. Pass any `ReactNode` to override (e.g. a sparkles icon for "premium" features).
featureIconColorenum'success'Palette role used to tint the feature bullet icon.
featuresreadonly ReactNode[]—Feature list — each item is rendered as a row with a bullet icon (`featureIcon`) on the leading edge. Plain strings are wrapped in `<Typography>`; pass any `ReactNode` for richer content (links, icons, etc.).
highlightedboolean—Marks this tier as the recommended one. Adds the highlight badge next to the `name`, switches the underlying `<Card>` to `variant="elevated" selected`, and tints the selection ring with the configured `color`.
highlightLabelReactNode'Most popular'Text shown in the highlight badge when `highlighted` is true. Set to `null` to suppress the badge while keeping the elevated/selected ring.
sizeenum'lg'Override the underlying `<Card>` `size` prop (drives padding density on Header / Body / Footer).
sxSx—Theme-aware inline style object — forwarded to the underlying `<Card>`.
variantenum—Override the underlying `<Card>` `variant` prop. Defaults track the `highlighted` flag (`'elevated'` when highlighted, `'outline'` otherwise) so the canonical recommended-tier visual works without configuration.