Steward
$120 / yearFor regulars who want to give back.
- Everything in Member
- Two guided walks
- Crew-day priority
Honest, comparable prices: inline prices, sales, menu figures, plan tiers and wholesale matrices.
import {
Pricing,
PricingAmount,
PricingCurrency,
PricingPeriod,
PricingPrice,
} from '@fairgarden-private/design/components/Pricing'
Every price is a data element with a machine-readable value. Show the unit and period next to every price, put fees on the price line, and never mark a sale with red alone: the strike and the word "Save" carry it. Dense module: rules and edges only, no ornament.
kind is required. inline sits in running text (pass render={<span />}) with the figure bold. sale strikes the original price in the muted ink, with a hidden "Was" for assistive technology, then the current price (label="Now") and the saving as a word. menu sets a mono figure, with optional superscript cents, for a leader row. Author the spaces between parts in your locale's order; PricingPeriod adds the thin space itself.
Guided walks from $12 per person, children free.
import {
Pricing,
PricingAmount,
PricingCents,
PricingCurrency,
PricingPeriod,
PricingPrice,
PricingQualifier,
PricingSave,
PricingStruck,
} from '@fairgarden-private/design/components/Pricing'
import styles from './prices.module.css'
/** Inline in running text, a sale (struck price, current price, the saving as a word) and a menu figure. */
export function PricingPrices() {
return (
<div className={styles.stack}>
<p className={styles.copy}>
Guided walks{' '}
<Pricing kind="inline" render={<span />}>
<PricingPrice value="12">
<PricingQualifier>from</PricingQualifier> <PricingCurrency>$</PricingCurrency>
<PricingAmount>12</PricingAmount>
<PricingPeriod>per person</PricingPeriod>
</PricingPrice>
</Pricing>
, children free.
</p>
<Pricing kind="sale">
<PricingStruck>
<PricingPrice value="480">
<PricingCurrency>$</PricingCurrency>
<PricingAmount>480</PricingAmount>
</PricingPrice>
</PricingStruck>
<PricingPrice value="180" label="Now">
<PricingCurrency>$</PricingCurrency>
<PricingAmount>180</PricingAmount>
</PricingPrice>
<PricingSave>Save $300</PricingSave>
</Pricing>
<Pricing kind="menu">
<PricingPrice value="4.50">
<PricingCurrency>$</PricingCurrency>
<PricingAmount>4</PricingAmount>
<PricingCents>50</PricingCents>
</PricingPrice>
</Pricing>
</div>
)
}
tiers holds a PricingTierList of two to four PricingTiers, each a faced card that opens a light face on every ground. recommended draws a 3 px frame and a "Recommended" badge across the top edge; author that tier first, because tiers stack below 1024 px of container and visual order follows source order. From 1024 px they sit side by side. When the plans feed a form, wrap the list in a RadioGroup and put each Radio in its PricingChoice: a checked Radio steps the edge to 3 px, and a disabled one gives a dotted edge and muted text (say "Unavailable" too). matrix is a table with row rules and a double-hair total rule; each PricingCell repeats its column name when rows become blocks below 360 px of container. Close either with PricingAsOf.
For regulars who want to give back.
Free parking and the quarterly newsletter.
Sold out for this season.
Prices as of 1 Sep 2026 (hemlockravine.org/join)
| Species | Retail | Wholesale |
|---|---|---|
| Pawpaw | Retail$18 | Wholesale$11 |
| Serviceberry | Retail$14 | Wholesale$9 |
| Minimum order | Retail1 | Wholesale25 |
import { Button } from '@fairgarden-private/design/components/Button'
import {
Pricing,
PricingAction,
PricingAmount,
PricingAsOf,
PricingCell,
PricingChoice,
PricingCurrency,
PricingFeature,
PricingFeatures,
PricingMatrix,
PricingPeriod,
PricingPrice,
PricingSummary,
PricingTier,
PricingTierList,
PricingTierName,
} from '@fairgarden-private/design/components/Pricing'
import { Radio } from '@fairgarden-private/design/components/Radio'
import { RadioGroup } from '@fairgarden-private/design/components/RadioGroup'
import styles from './tiers.module.css'
const tiers = [
{
value: 'steward',
name: 'Steward',
amount: '120',
summary: 'For regulars who want to give back.',
features: ['Everything in Member', 'Two guided walks', 'Crew-day priority'],
recommended: true,
},
{
value: 'member',
name: 'Member',
amount: '45',
summary: 'Free parking and the quarterly newsletter.',
features: ['Free parking', 'Quarterly newsletter'],
},
{
value: 'patron',
name: 'Patron',
amount: '500',
summary: 'Sold out for this season.',
features: ['Everything in Steward', 'Annual preserve tour'],
unavailable: true,
},
]
/** A membership tier set chosen with a Radio group, then a wholesale matrix. */
export function PricingTiers() {
return (
<div className={styles.stack}>
<Pricing kind="tiers">
<RadioGroup aria-label="Membership level" defaultValue="steward">
<PricingTierList>
{tiers.map((tier) => (
<PricingTier key={tier.value} recommended={tier.recommended}>
<PricingTierName>{tier.name}</PricingTierName>
<PricingPrice value={tier.amount}>
<PricingCurrency>$</PricingCurrency>
<PricingAmount>{tier.amount}</PricingAmount>
<PricingPeriod>/ year</PricingPeriod>
</PricingPrice>
<PricingSummary>{tier.summary}</PricingSummary>
<PricingFeatures>
{tier.features.map((feature) => (
<PricingFeature key={feature}>{feature}</PricingFeature>
))}
</PricingFeatures>
<PricingChoice>
<Radio value={tier.value} disabled={tier.unavailable}>
Choose {tier.name}
</Radio>
</PricingChoice>
{tier.unavailable ? <PricingAction>Unavailable</PricingAction> : null}
</PricingTier>
))}
</PricingTierList>
</RadioGroup>
<PricingAsOf>Prices as of 1 Sep 2026 (hemlockravine.org/join)</PricingAsOf>
</Pricing>
<Pricing kind="matrix">
<PricingMatrix label="Seedling prices">
<thead>
<tr>
<th scope="col">Species</th>
<th scope="col">Retail</th>
<th scope="col">Wholesale</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Pawpaw</th>
<PricingCell label="Retail">$18</PricingCell>
<PricingCell label="Wholesale">$11</PricingCell>
</tr>
<tr>
<th scope="row">Serviceberry</th>
<PricingCell label="Retail">$14</PricingCell>
<PricingCell label="Wholesale">$9</PricingCell>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Minimum order</th>
<PricingCell label="Retail">1</PricingCell>
<PricingCell label="Wholesale">25</PricingCell>
</tr>
</tfoot>
</PricingMatrix>
</Pricing>
<Button variant="solid">Join Now</Button>
</div>
)
}
Light grounds are the home for tiers; inside a field, tier faces become light islands. Each tier's rest edge is its face's own edge color, so it stays visible on every ground. Saturated grounds hold a single price only. In print the recommended frame keeps 2.25 pt and other edges 0.75 pt, the badge prints as a word above its tier, and actions print their short URLs.
The pricing module. Inline, menu and sale prices hold a PricingPrice
(and, for a sale, a PricingStruck and a PricingSave); pass
render={<span />} inside running text. A tier set holds a
PricingTierList of PricingTiers; a matrix a PricingMatrix. Close a
tier set or matrix with PricingAsOf. Show the unit and period with
every price, never mark a sale with red alone, and never hide fees.
| Prop | Type | Description |
|---|---|---|
kind | | Required. |
primary | | Primary Radix scale: prices, strikes, frames and checks. Never defaulted; omitted, it inherits the scope [D133]. |
secondary | | Secondary Radix scale: the tier Radio’s selection ( |
One price, a data element: PricingQualifier (“From”), PricingCurrency,
PricingAmount, PricingCents and PricingPeriod, in the locale’s
order (author the spaces). Kept on one line.
| Prop | Type | Description |
|---|---|---|
label | | Words for assistive technology only, read before the price: “Now” for the current price of a sale. |
value | | The machine-readable amount, e.g. |
The qualifier, e.g. “From”: regular weight, before the figure.
The currency sign: with the figure inline; type-caption beside a tier’s figure.
The figure, in data numerals: bold in body text (inline, sale), mono
(menu), type-stat-compact in a tier.
Optional retail cents as a superscript at 60%, never below --font-size-0.
The period or unit, e.g. “/ month”, after a thin space the part adds; on
the figure’s line, type-caption in a tier.
The original price of a sale, struck through at --ds-stroke-1-5 in
--role-muted (never a status color), with a hidden “Was” for assistive
technology. Put a PricingPrice inside.
| Prop | Type | Description |
|---|---|---|
label | | Words for assistive technology only, read before the struck price. Default “Was”. |
The saving as a word, “Save $300″, in --primary12: the word carries the sale, not a color.
The tier set’s layout, queried on the pricing container: stacked below
1024 px of container (author the recommended tier first, since visual
order follows source order), columns of one comparison row from 1024,
capped at --ds-container-content. When the plans feed a form, wrap it
in a RadioGroup and put each tier’s Radio in its PricingChoice.
One plan: a faced card (article) holding PricingTierName, a
PricingPrice, PricingSummary, PricingFeatures, an optional
PricingChoice (a Radio, when the plan feeds a form) and a
PricingAction (a Button, “Choose Basic”). Selected and unavailable
states follow the Radio’s data-checked and data-disabled.
| Prop | Type | Description |
|---|---|---|
badge | | The badge word straddling the top edge. Defaults to “Recommended” on
the recommended tier; pass your own words, or |
frameProps | | Props for the tier’s frame, the faced Card (an |
recommended | | Marks the recommended plan: a |
The plan’s name: type-itemhead in --primary12. Renders <h3>.
The one-line summary: type-body-ui in --primary12.
The feature list, in full under each tier. Omit excluded features; never strike them.
One included feature, led by the inline-tier check icon (hidden: the words carry it).
The plan choice, the tier Card’s CardChoice: put the tier’s Radio here
(its group wraps the tier list). Its data-checked selects the tier’s
edge; its data-disabled marks the tier unavailable (then say
“Unavailable” in PricingAction).
The tier’s action: a Button with a title-case label (“Start Free Trial”), or the word “Unavailable”. Prints as “Label (short URL)".
The price matrix: a §8.2 table (rows by species or size × retail and
wholesale columns) with --role-rule row rules and a line-double-hair
total rule above the tfoot (the minimum order). Author thead,
tbody and tfoot; use PricingCell with a label so each value keeps
its column name when rows become blocks below 360 px of container.
| Prop | Type | Description |
|---|---|---|
label | | Names the scrolling wrapper for assistive technology, which then is a
focusable |
A matrix value cell; its label repeats the column header in the block form.
| Prop | Type | Description |
|---|---|---|
label | | The column’s name (“Wholesale”), shown before the value only when rows are blocks. |
The provenance line, “Prices as of 1 Sep 2026 (short URL)": type-caption
after a tier set or matrix, on screen and in print (§12.9, P10).
Props for PricingAction: div props and render.
type PricingActionProps = useRender.ComponentProps<'div'>Props for PricingAsOf: paragraph props and render.
type PricingAsOfProps = useRender.ComponentProps<'p'>Props for PricingCell: td props, render and the column name.
type PricingCellProps = React.ClassAttributes<HTMLTableDataCellElement> &
React.TdHTMLAttributes<HTMLTableDataCellElement> &
__type & { label?: React.ReactNode }Props for PricingChoice: div props and render.
type PricingChoiceProps = useRender.ComponentProps<'div'>Props for PricingFeature: list-item props and render.
type PricingFeatureProps = useRender.ComponentProps<'li'>Props for PricingFeatures: list props and render.
type PricingFeaturesProps = useRender.ComponentProps<'ul'>The five pricing builds (§12.9).
type PricingKind = 'inline' | 'menu' | 'sale' | 'tiers' | 'matrix'Props for PricingMatrix: table props, render and the scroll region’s name.
type PricingMatrixProps = React.ClassAttributes<HTMLTableElement> &
React.TableHTMLAttributes<HTMLTableElement> &
__type & { label?: string }Props for the small price parts: span props and render.
type PricingPartProps = useRender.ComponentProps<'span'>Props for PricingPrice: data props, render and the hidden label.
type PricingPriceProps = React.ClassAttributes<HTMLDataElement> &
React.DataHTMLAttributes<HTMLDataElement> &
__type & { value?: string | number; label?: React.ReactNode }Props for Pricing: div props, render, the kind and the color axes.
type PricingProps = React.ClassAttributes<HTMLDivElement> &
React.HTMLAttributes<HTMLDivElement> &
__type & {
kind: PricingKind;
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}Props for PricingStruck: s props, render and the hidden label.
type PricingStruckProps = React.ClassAttributes<HTMLElement> &
React.HTMLAttributes<HTMLElement> &
__type & { label?: React.ReactNode }Props for PricingSummary: paragraph props and render.
type PricingSummaryProps = useRender.ComponentProps<'p'>Tier classes: recommended draws the --ds-stroke-3 --primary12 frame and the badge.
type pricingTier = pricingTierProps for PricingTierList: list props and render.
type PricingTierListProps = useRender.ComponentProps<'ul'>Props for PricingTierName: heading props and render (default <h3>).
type PricingTierNameProps = useRender.ComponentProps<'h3'>Props for PricingTier: list-item props, render, recommended and the badge word.
type PricingTierProps = React.ClassAttributes<HTMLLIElement> &
React.LiHTMLAttributes<HTMLLIElement> &
__type &
VariantProps<__type> & {
recommended?: boolean;
badge?: React.ReactNode;
frameProps?: Omit<useRender.ComponentProps<'article', {}, HTMLProps>, 'children'>;
}Specification: DESIGN-SYSTEM.md §12.9 (pricing, v1 per D169) and §5.10.2 (module reflow).