Program
Hero
The page's one opening statement and its single h1. A plain title block counts. Never text on a photo, never sized to the viewport, never animated on load.
import { Hero, HeroLockup } from '@fairgarden-private/design/components/Hero'
The hero is a band: a page ground (by default the one it sits on), or preset="night" as the media hero. Pass wayfinding (a breadcrumb, eyebrow or badge), title, lede and actions: one solid pill at size="lg" and at most its outline twin. Below 360 px the pair stacks, primary first. Render it inside main.
The exit seam. When something crosses the hero's bottom edge (the stacked photo, the entry cards, the rail's trail), or when the night hero ends on the hill or the fringe, the hero also renders the top of the next band, on next. That keeps the parts that land there in its colors. Open your next band on the same page ground.
Editorial and the media hero
Editorial and the media hero
A is type only: wayfinding, the display title (about 20ch, 2–3 lines), the lede and the actions, in the reading span. landing allows the larger display size from 1024 px. With a photo it becomes the centred media hero on the night band, with the captioned photo below the text. From 1024 px a photo hero may carry up to three entries: compact cards with a square image, a caps category and one link (each a Card kind="entry"), each 4 of 12 columns, straddling the bottom edge. Below 1024 px they stack under the hero. edge="hill" ends the night hero on the hill in light mode.
Annual report
Land for good, for everyone
Ten years of protected acres, open trails and neighbors who kept them.
The prairie, one acre at a time
A reference page's media hero: the photo figure under centred text.
Events
Resources
The white band below opens with its section space; the cards stack here below 1024 px.
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Hero } from '@fairgarden-private/design/components/Hero'
import { photo, square } from '../photo'
import styles from './editorial.module.css'
/**
* A on paper, then A as the media hero on the night band with three entry
* cards across its edge into the white band below.
*/
export function HeroEditorial() {
return (
<div className={styles.page}>
<Hero
wayfinding={<p className={styles.eyebrow}>Annual report</p>}
title={
<>
Land for good, <em>for everyone</em>
</>
}
lede="Ten years of protected acres, open trails and neighbors who kept them."
actions={
<>
<Button variant="solid" size="lg">
Read the Report
</Button>
<Button size="lg">Download PDF</Button>
</>
}
/>
<Hero
preset="night"
next="white"
title="The prairie, one acre at a time"
lede="A reference page's media hero: the photo figure under centred text."
photo={<img src={photo} alt="Tallgrass prairie at dawn under a low sun" />}
caption="Nachusa Grasslands, Illinois. Photo: A. Rivera"
entries={[
{
href: '#editorial',
category: 'Program',
title: 'Protect your land',
image: <img src={square} alt="" />,
},
{
href: '#editorial',
category: 'Events',
title: 'Rally 2026',
image: <img src={square} alt="" />,
},
{
href: '#editorial',
category: 'Resources',
title: 'The learning center',
image: <img src={square} alt="" />,
},
]}
/>
<Ground kind="band" preset="white" className={styles.band}>
<div className={styles.container}>
<p className={styles.text}>
The white band below opens with its section space; the cards stack here below 1024 px.
</p>
</div>
</Ground>
</div>
)
}
Split photo
Split photo
C sets a flat text band beside the photo from 1024 px: text in 5 columns, the photo in 7, top-aligned, reaching the container edge. Below 1024 px the photo follows the text, inset 24 px. The caption sits right-flush below it: the description, then the credit. photoAction puts an icon-only media Button on the photo's corner, and plate an opaque plate over its lower edge. rail pins "Explore the land" to the hero's bottom corner from 768 px, reading upward, flush with the viewport edge. Its dashed trail crosses into the next band from 1024 px. Below 768 px the rail is an inline link under the lede.
Keeping farms in farming
Easements that let a family keep working the land, and keep it open for good.
The trail lands in this band, in its accent.
import { Breadcrumb } from '@fairgarden-private/design/components/Breadcrumb'
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Hero } from '@fairgarden-private/design/components/Hero'
import { photo } from '../photo'
import styles from './split.module.css'
/**
* C on the night band: the text beside the photo from 1024 px, a media
* Button on the photo, and the explore rail with its trail into paper.
*/
export function HeroSplit() {
return (
<div className={styles.page}>
<Hero
kind="split"
preset="night"
next="paper"
wayfinding={<Breadcrumb items={[{ label: 'Programs', href: '#split' }]} current="Farmland" />}
title="Keeping farms in farming"
lede="Easements that let a family keep working the land, and keep it open for good."
actions={
<Button variant="solid" size="lg">
Talk to Us
</Button>
}
photo={<img src={photo} alt="A family walking a hayfield at the edge of a wood" />}
caption="The Okafor farm, Wisconsin. Photo: J. Lee"
photoAction={
<Button iconOnly onMedia icon="zoom_in">
Enlarge the Photo
</Button>
}
rail={{ href: '#split', label: 'Explore the land' }}
/>
<Ground kind="band" preset="paper" className={styles.band}>
<div className={styles.container}>
<p className={styles.text}>The trail lands in this band, in its accent.</p>
</div>
</Ground>
</div>
)
}
C, stacked
C, stacked
For editorial and reference pages, stacked keeps the reading axis centred. From 1024 px the text band sits in the reading span, then the photo in the same span crosses the night band's edge at its middle. The hill passes behind it, and the caption sits right-flush below it on the page ground. On a dark page the hill drops for the straight seam and the photo still straddles it.
The birds came back to Boone Creek
Five years after the dams came out, the creek's thrushes are nesting again.
The article begins here, on the page ground.
import { Breadcrumb } from '@fairgarden-private/design/components/Breadcrumb'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Hero } from '@fairgarden-private/design/components/Hero'
import { photo } from '../photo'
import styles from './stacked.module.css'
/**
* "C, stacked": the text band in the reading span, then the photo crossing
* the night band's edge, the hill behind it and the caption on paper.
*/
export function HeroStacked() {
return (
<div className={styles.page}>
<Hero
kind="split"
stacked
preset="night"
edge="hill"
next="paper"
wayfinding={
<Breadcrumb items={[{ label: 'News', href: '#stacked' }]} current="Stories" />
}
title="The birds came back to Boone Creek"
lede="Five years after the dams came out, the creek's thrushes are nesting again."
photo={<img src={photo} alt="A wooded creek bend in early summer" />}
caption="Boone Creek, Kentucky, in June. Photo: M. Chen"
/>
<Ground kind="band" preset="paper" className={styles.band}>
<div className={styles.container}>
<p className={styles.text}>The article begins here, on the page ground.</p>
</div>
</Ground>
</div>
)
}
Illustrated
Illustrated
B pairs the text with a sticker drawing and its halo. At base the drawing sits on the headline, centred, at 152 px. From 1024 px the text takes 7 columns and the drawing, at 312 px, takes 5. It is the only hero that may carry a Display Lockup: pass <HeroLockup caps="…" accent="…" /> as the title. field sets the lockup and drawing in the page's one leaf or amber campaign field.
Grown right here
Burgers from farms within a hundred miles, cooked on the corner since 2009.
The rail’s trail crosses into this band.
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Hero, HeroLockup } from '@fairgarden-private/design/components/Hero'
import styles from './illustrated.module.css'
/** B on paper: the Display Lockup and a sticker in the page's leaf campaign field. */
export function HeroIllustrated() {
return (
<div className={styles.page}>
<Hero
kind="illustrated"
field="leaf"
next="paper"
title={<HeroLockup caps="Grown right" accent="here" />}
lede="Burgers from farms within a hundred miles, cooked on the corner since 2009."
actions={
<Button variant="solid" size="lg">
Order Now
</Button>
}
drawing={
<svg viewBox="0 0 312 312" role="img" aria-label="A sprouting seed">
<circle cx="156" cy="170" r="104" fill="var(--role-halo)" />
<path
d="M156 250v-86m0 0c0-40 28-64 64-64-4 40-28 64-64 64Zm0 0c0-34-24-56-58-56 4 34 26 56 58 56Z"
fill="none"
stroke="var(--primary12)"
strokeWidth="6"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M96 250h120"
stroke="var(--primary12)"
strokeWidth="6"
strokeLinecap="round"
/>
</svg>
}
rail={{ href: '#illustrated', label: 'Find a kitchen' }}
/>
<Ground kind="band" preset="paper" className={styles.band}>
<div className={styles.container}>
<p className={styles.text}>The rail’s trail crosses into this band.</p>
</div>
</Ground>
</div>
)
}
Technical
Technical
D frames the title, a rule, a mono subtitle and the actions in a double rule. From 1024 px an aside column sits behind a vertical rule. The cells row reflows on its own width: one column below 360 px, 2-up from 360 px, 4-up from 480 px. Any dot grid or graph pattern stays outside the frame.
Respinner
v2.4.0 · MIT · 4 kB
Install
npm i respinner- Size
- 4 kB
- Dependencies
- 0
- Browsers
- 98%
- License
- MIT
import { Button } from '@fairgarden-private/design/components/Button'
import { Hero } from '@fairgarden-private/design/components/Hero'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './technical.module.css'
/** D on white: the double frame, the install column and the info cells; the dot grid stays outside the frame. */
export function HeroTechnical() {
return (
<Hero
kind="technical"
preset="white"
className={patterns.patternDotgrid}
title="Respinner"
lede="v2.4.0 · MIT · 4 kB"
actions={
<>
<Button variant="solid" size="lg">
Get Started
</Button>
<Button size="lg">Read the Docs</Button>
</>
}
aside={
<div className={styles.install}>
<p className={styles.label}>Install</p>
<code className={styles.code}>npm i respinner</code>
</div>
}
cells={[
{ label: 'Size', value: '4 kB' },
{ label: 'Dependencies', value: '0' },
{ label: 'Browsers', value: '98%' },
{ label: 'License', value: 'MIT' },
]}
/>
)
}
API Reference
The page’s one hero, with the single h1. Never text on a photo, never
sized to the viewport, never animated on load. A photo hero splits: text
on a flat ground, the photo as its own captioned figure.
editorial(A): type only on the page ground; with aphoto, the centred media hero, onpreset="night".illustrated(B): text and a sticker drawing;fieldsets them in a campaign field;HeroLockupis its Display Lockup.split(C): text beside the photo from--lg-n-above;stackedputs the photo under the text in the reading span, straddling the edge.technical(D): the double-ruled frame, withasideandcells.
Straddles [D185]: entries (up to 3) cross the bottom edge from
--lg-n-above and stack under the hero below it; the stacked photo
crosses it too, with the hill behind. rail pins the rotated label to
the bottom end corner, its trail crossing the seam. The hero then renders
its exit seam on next, the band below. Prints as the masthead title.
| Prop | Type | Description |
|---|---|---|
actions | | The action Buttons: one |
aside | | |
caption | | A content photo figure’s caption: description, then “Photo: Name”; right-flush below. |
cells | | |
drawing | | |
edge | | |
entries | | Up to 3 entry cards straddling the hero’s bottom edge from |
field | | |
kind | | A: type only, or the media hero’s photo figure (centred, on |
landing | | Landing pages: the title may take |
lede | | The lede: |
next | | The page ground of the band below the hero, which the exit seam takes
(the stacked photo’s lower half and caption, the entry cards, the
trail and the hill all land on it). Default: the enclosing page
ground, else |
photo | | The photo, usually an |
photoAction | | An icon-only |
plate | | |
preset | | The hero’s band: a page ground (default: the enclosing page ground,
else |
primary | | Primary Radix scale for the hero’s scope. Never defaulted; omitted, the preset’s default [D133]. |
rail | | The explore rail: an inline link under the lede at base; the rotated corner label from |
renderLink | | Builds each anchor (rail, entry cards), e.g. |
secondary | | Secondary Radix scale for the hero’s scope: the title, the trail and the accents. |
stacked | | |
title | | The |
titleId | | The title’s |
transactional | | A transactional hero: the actions carry the page’s single
|
wayfinding | | Wayfinding above the title: a §9.8 Breadcrumb, an eyebrow, a pill badge or a topic tag. |
The Display Lockup (§3.8) as the illustrated hero’s title: caps words and
one accent word, real text in the h1. Pass it as title.
| Prop | Type | Description |
|---|---|---|
accent | | The one accent word, in |
caps | | The caps words, in |
Additional types
HeroCell
One technical info cell: a caps label over a mono value.
type HeroCell = { label: React.ReactNode; value: React.ReactNode }HeroEntries
HeroEntry
One compact entry card [D185], rendered as Card kind="entry": square image, caps category, item-head link.
type HeroEntry = {
/** The card's one link. */
href: string;
/** The category, in `type-label` caps. */
category: React.ReactNode;
/** The link text, in `type-itemhead`. */
title: React.ReactNode;
/** A square image, usually an `img` with its `alt`. */
image?: React.ReactNode;
}HeroKind
The four §11.9 builds: A editorial, B illustrated, C split photo, D technical.
type HeroKind = 'editorial' | 'illustrated' | 'split' | 'technical'<'editorial' | 'illustrated' | 'split' | 'technical' | null | undefined>HeroLockupProps
Props for HeroLockup.
type HeroLockupProps = {
/** The caps words, in `type-lockup-caps`. */
caps: React.ReactNode;
/** The one accent word, in `type-lockup-accent`. */
accent: React.ReactNode;
}HeroProps
Props for Hero: header props, the kind with its parts, the preset (and
the night hero’s edge), next, the flags and the color axes.
type HeroProps = (
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind?: 'editorial';
photo?: React.ReactNode;
entries?: HeroEntries;
stacked?: undefined;
plate?: undefined;
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
edge?: undefined;
}
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind?: 'editorial';
photo?: React.ReactNode;
entries?: HeroEntries;
stacked?: undefined;
plate?: undefined;
preset: 'night';
edge?: 'hill' | 'fringe';
}
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind: 'split';
photo: React.ReactNode;
stacked: true;
entries?: undefined;
plate?: undefined;
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
edge?: undefined;
}
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind: 'split';
photo: React.ReactNode;
stacked: true;
entries?: undefined;
plate?: undefined;
preset: 'night';
edge?: 'hill' | 'fringe';
}
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind: 'split';
photo: React.ReactNode;
stacked?: false;
entries?: HeroEntries;
plate?: React.ReactNode;
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
edge?: undefined;
}
| {
caption?: React.ReactNode;
photoAction?: React.ReactNode;
rail?: HeroRail;
drawing?: undefined;
field?: undefined;
aside?: undefined;
cells?: undefined;
kind: 'split';
photo: React.ReactNode;
stacked?: false;
entries?: HeroEntries;
plate?: React.ReactNode;
preset: 'night';
edge?: 'hill' | 'fringe';
}
| {
kind: 'illustrated';
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
edge?: undefined;
drawing?: React.ReactNode;
field?: 'leaf' | 'amber';
rail?: HeroRail;
stacked?: undefined;
photo?: undefined;
caption?: undefined;
photoAction?: undefined;
plate?: undefined;
entries?: undefined;
aside?: undefined;
cells?: undefined;
}
| {
kind: 'technical';
preset?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
edge?: undefined;
aside?: React.ReactNode;
cells?: HeroCell[];
drawing?: React.ReactNode;
stacked?: undefined;
photo?: undefined;
caption?: undefined;
photoAction?: undefined;
plate?: undefined;
entries?: undefined;
field?: undefined;
rail?: undefined;
}
) & {
/** Landing pages: the title may take `type-display-xl` from `--lg-n-above`. Default `false`. */
landing?: boolean;
/**
* A transactional hero: the actions carry the page's single
* `--ds-size-control-xl` pill (pass `size="xl"` to its Button). Default `false`.
*/
transactional?: boolean;
/** Primary Radix scale for the hero's scope. Never defaulted; omitted, the preset's default [D133]. */
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale for the hero's scope: the title, the trail and the accents. */
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;
/** Wayfinding above the title: a §9.8 Breadcrumb, an eyebrow, a pill badge or a topic tag. */
wayfinding?: React.ReactNode;
/** The `h1`: `type-display`, about 20ch, in 2–3 lines. Real text, never on a photo. */
title: React.ReactNode;
/** The title's `id`. */
titleId?: string;
/** The lede: `type-lead` in `--primary12` (a mono subtitle in `technical`). */
lede?: React.ReactNode;
/** The action Buttons: one `solid` pill (`size="lg"`) and at most its outline twin. */
actions?: React.ReactNode;
/**
* The page ground of the band below the hero, which the exit seam takes
* (the stacked photo's lower half and caption, the entry cards, the
* trail and the hill all land on it). Default: the enclosing page
* ground, else `paper`. Open the next band on the same preset.
*/
next?: 'paper' | 'white' | 'tide' | 'meadow' | 'pollen' | 'apricot' | 'rose' | 'heather';
/** Builds each anchor (rail, entry cards), e.g. `(href) => <NextLink href={href} />`. */
renderLink?: (href: string) => ReactElement;
}HeroRail
The explore rail [D185]: the rotated rail label and its optional compass.
type HeroRail = {
/** Where the rail goes, usually an in-page anchor. */
href: string;
/** The label, e.g. "Explore the land". */
label: React.ReactNode;
/** An optional compass: an icon-only §9.2 Button that scrolls with the hero, never a fixed launcher. */
compass?: React.ReactNode;
}Specification: DESIGN-SYSTEM.md §11.9 (hero), §5.6.2 (straddles) and D185 (entry cards, the explore rail, "C, stacked").