Skip to contentAsheeUIAsheeUI

PricingCard

One plan, its name, what it costs, what it includes and where to choose it, which is the unit a pricing table is built from.

A pricing page is a comparison, and PricingCard is the thing being compared: one plan's name, its amount, the features it includes and the action that chooses it. The card is the unit rather than the table, because a pricing table is a layout decision, one plan per row on a phone, three across on a desktop, and the page places them in a Grid rather than having the framework pick a shape for it. One card on its own is legitimate too: the plan a product sells, stated in the middle of a landing page.

It belongs to the marketing sections family, so its heading block and its configured actions behave the way every other band's do.

Usage

import { PricingCard } from "@asheeui/web";

<PricingCard
  name="Growth"
  price="£29"
  period="/month"
  features={[{ label: "25,000 contacts" }]}
  primaryAction={{ label: "Choose Growth", href: "/signup" }}
/>

name and price are required; everything else is a plan stating more about itself. A table is a grid of these, one child per plan, so the rows line up across the cards and the recommended one is marked with highlighted and a badge.

The amount and the period

price and period are two props because they are two things: an amount is a figure and a period is a unit, and the two are typeset differently on one baseline. price is a node rather than a number, £29, Free, Custom, from £0.02 per email are all amounts a plan states, and the component does not parse currency, because a framework that guesses at money will eventually charge somebody the wrong amount. period is optional: a one-off price, a lifetime licence and a "contact us" plan have no unit to show.

features is a list of PricingFeatureItem, and a line with included: false is drawn as excluded rather than dropped, so two plans can be read line for line and a reader can see what the cheaper one does not have. The excluded line also carries the words "(not included)" for assistive technology, so the cross beside it is not the only thing saying so.

highlighted draws a ring in the primary colour and variant decides the surface it sits on: bordered for a plan among equals, elevated for the one a reader should look at first. Neither is a substitute for saying so, put the reason in badge, above the plan name, so the recommendation is readable rather than merely coloured, and so it still exists for a reader who cannot see the ring.

size is density: the card's padding and the gaps between its parts, on the same scale as every other card in the framework. A plan does not grow, centre or lift itself against its neighbours, that alignment is the Grid's, which is what keeps a row of plans reading as one table.

Props

Two options are registered, variant bordered and size lg, and resolve from the instance, then components.pricingcard, then the registration. They are the two a product restates, because they are the plan's dress. highlighted is deliberately not one of them, and cannot be: a recommendation is a statement about one plan in one table rather than about the product, so it is an instance prop, and a components.pricingcard.highlighted would mark every plan as the recommended one.

The card is the framework's Card, which is where its rounding and its surface come from. Rounding is not a pricing-card option at all: it resolves through the card's cascade, components.card.radius, then the application's defaultRadius, which the framework ships as xs, then the card's own md fallback, so a product that rounds every surface states it once and every plan follows. The surface is the treatment variant names, drawn by the card. What the plan does state itself is its content, name, price, period, description, badge, features, the two actions and highlighted, together with titleAs, which is where the plan name's level in the outline comes from.

Accessibility

  • The plan's name is the card's title, and a card's title is a level-three heading by default, so a pricing table belongs under a section h2 and each plan becomes an h3. The band forwards titleAs to the card, so a plan that sits somewhere else in the outline, under a card's h3, or directly below the page's h1, states its own level rather than inheriting one that does not fit.
  • The amount is not a heading: it renders as a styled span inside the card, so a reader navigating by heading hears the plan and its features, not every price read out as a section title.
  • Each feature is a list item, and an excluded line states "(not included)" in text as well as drawing a cross, so the feature list survives a reader who cannot see the marker.
  • A configured action becomes a link when it carries href, or a router component, and a <button> when it does not. A plan chosen through a handler rather than a destination puts that Button in children, because a configured action takes no onClick.

The heading block, the actions as configuration and the data-not-markup body are stated once in Marketing sections.