Patterns
Textures and fills as shared CSS classes, drawn in SVG and colored only by the scope's role variables, so they follow the ground and the page mode on their own.
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
Add one class to an element, or compose it into a part of your own module. The element keeps its content on top: the marks sit on a layer between its ground and its children.
.face {
composes: patternGrain from '@fairgarden-private/design/utils/pattern.module.css';
}There are two kinds. Decorative patterns (patternDotgrid, patternSpeckle, patternSunburst, patternGrain, patternAwning, and the ornamentFringe edge) are texture: they drop out in print and in forced colors. Semantic fills (patternHatch*, patternDotscreen*) carry meaning in charts, maps and legends: each has a 1 px outline and prints black.
Dot grid
Dot grid and dot field
patternDotgrid draws 1.5 px dots every 16 px from the content edge, so dots land on the column edges. On forest, night and amber it becomes the dot field, which carries display type and one action. patternDotgridFine (1 px at 8 px) is used only on high-density screens and falls back to the standard grid elsewhere. Add patternPlate on a white figure plate to draw the dots in the hairline color. Never put running text on the dots: set it on a solid plate.
Field Notes
Field Notes
Field Notes
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { PresetGround } from '@/components/PresetGround'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './dotgrid.module.css'
const presets = ['paper', 'forest', 'amber'] as const
/**
* The dot grid in --role-tint on a light ground and as the dot field on the
* forest and amber fields [D177]: display type and one action on the dots,
* any other text on a solid plate (a paper face, a light island inside a
* field). On a white figure plate the dots take --role-hairline
* (patternPlate).
*/
export function PatternDotgrid() {
return (
<div className={styles.stack}>
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround
key={preset}
preset={preset}
className={`${styles.field} ${patterns.patternDotgrid}`}
>
<p className={styles.display}>Field Notes</p>
<Button variant="solid" size="sm">
Explore
</Button>
<Ground preset="paper" kind="face" className={styles.plate}>
{preset}
</Ground>
</PresetGround>
))}
</div>
<Ground
preset="white"
kind="face"
render={<figure />}
className={`${styles.figure} ${patterns.patternDotgrid} ${patterns.patternPlate}`}
>
<figcaption className={styles.plate}>Figure 1. A specimen plate on the hairline grid.</figcaption>
</Ground>
</div>
)
}
Speckle
Speckled bands
patternSpeckle scatters one mark in every 16 px cell, nudged up to 4 px off center: mostly 2 px dots, with a few short seed strokes and commas at random angles. It is band texture, in the ground's tint: put it on one full-bleed band's Ground or one field per page, or on a map or media plate. On the solid fields it runs tone-on-tone. Main-ink text sits on it at any size.
Land for good
Land for good
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './speckle.module.css'
/**
* The speckle: jittered dots and seed flecks in --role-tint, one mark per
* 16 px cell. One full-bleed band or one field per page; on a solid field
* it is tone-on-tone [D177].
*/
export function PatternSpeckle() {
return (
<div className={styles.stack}>
<Ground preset="paper" className={`${styles.band} ${patterns.patternSpeckle}`}>
<p className={styles.display}>Land for good</p>
<Button variant="solid">Our Mission</Button>
</Ground>
<Ground kind="field" preset="leaf" className={`${styles.band} ${patterns.patternSpeckle}`}>
<p className={styles.display}>Land for good</p>
<Button variant="solid">Our Mission</Button>
</Ground>
</div>
)
}
Sunburst
Sunburst band
patternSunburst draws 60 dotted rays, 6° apart and 290 px long, from the element's bottom center, and the element's edges clip them, so the bottom edge cuts the burst in half like a rising sun. Near the center the marks are 2.5 px dots; farther out they lengthen to 4, 6, 8 and 10 px dashes. All marks start on shared radii 6.5 px apart, so rings of dots form across the rays. Lay the element out so its bottom edge sits 40 px below the call to action. Use it behind the page's one CTA only (the CTA Block's sunburst kind draws it for you), with the heading in the main ink and never on a band with a shaped edge.
Find a land trust near you
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './sunburst.module.css'
/**
* The sunburst: dotted rays from the band's bottom centre, clipped by its
* bottom edge, behind the page's one CTA (CTABlock draws it by default).
*/
export function PatternSunburst() {
return (
<Ground preset="white" className={`${styles.band} ${patterns.patternSunburst}`}>
<p className={styles.display}>Find a land trust near you</p>
<Button variant="solid" size="lg" icon="arrow_forward">
Find a Land Trust
</Button>
</Ground>
)
}
Grain
Grain on deep and saturated faces
patternGrain is the stipple: fine flecks a little lighter and a little darker than the ground, on one card face per page. It takes effect on deep and saturated grounds only; on a light ground the fleck colors equal the ground and nothing shows. Text on grain stays in the ground's main ink.
Join the Count
A forest field.
Join the Count
The night band.
Join the Count
A leaf field.
Join the Count
A amber field.
Join the Count
A clay field.
Join the Count
A pink field.
Join the Count
A royal field.
Join the Count
A brick field.
import { PresetGround } from '@/components/PresetGround'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './grain.module.css'
const presets = ['forest', 'night', 'leaf', 'amber', 'clay', 'pink', 'royal', 'brick'] as const
/**
* Grain, the stipple: light and dark flecks from the scope's grain roles,
* on the fields and the night band [D177]. Pink shows light flecks only and
* royal dark flecks only; --primary12 text holds 4.5:1 over every fleck.
*/
export function PatternGrain() {
return (
<div className={styles.row}>
{presets.map((preset) => (
<PresetGround
key={preset}
preset={preset}
className={`${styles.face} ${patterns.patternGrain}`}
>
<p className={styles.head}>Join the Count</p>
<p className={styles.body}>
{preset === 'night' ? 'The night band.' : `A ${preset} field.`}
</p>
</PresetGround>
))}
</div>
)
}
Awning
Awning bands
patternAwning alternates 24 px stripes of the ground and its tint, with a straight band edge. Use it on campaign bands only, once per page, behind display type and one action.
Summer Market
Summer Market
import { Button } from '@fairgarden-private/design/components/Button'
import { Ground } from '@fairgarden-private/design/components/Ground'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './awning.module.css'
/**
* The awning: 24 px stripes of the ground and --role-tint, starting at the
* content edge. One per page, carrying display type and one action; on a
* campaign field it is tone-on-tone [D177].
*/
export function PatternAwning() {
return (
<div className={styles.stack}>
<Ground preset="paper" className={`${styles.band} ${patterns.patternAwning}`}>
<p className={styles.display}>Summer Market</p>
<Button variant="solid">Get Tickets</Button>
</Ground>
<Ground kind="field" preset="clay" className={`${styles.band} ${patterns.patternAwning}`}>
<p className={styles.display}>Summer Market</p>
<Button variant="solid">Get Tickets</Button>
</Ground>
</div>
)
}
Hatched fringe
Hatched fringe
ornamentFringe goes on the lower of two bands on a campaign page: a row of short, uneven ticks hangs from its top edge in the band's ink. It counts as the page's one shaped edge and its one decorative separator. In print, and wherever the band shows its seam rule, a straight rule replaces it. Don't combine it with patternGrain on one element.
The page band above the seam.
Field Notes
import { Ground } from '@fairgarden-private/design/components/Ground'
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './fringe.module.css'
/**
* The hatched fringe: jittered 1.5 px ticks hanging from the night band's
* top edge, in its --primary12. A shaped edge sits only on a seam that tone
* separates, so it shows in light mode only; in dark mode, print and forced
* colors the band draws its straight seam rule instead [D179].
*/
export function PatternFringe() {
return (
<div className={styles.stack}>
<Ground preset="paper" className={styles.band}>
<p className={styles.body}>The page band above the seam.</p>
</Ground>
<Ground preset="night" className={`${styles.band} ${patterns.ornamentFringe}`}>
<p className={styles.display}>Field Notes</p>
</Ground>
</div>
)
}
Hatch and dot screen
Hatch and dot screen
Hatch means partial, projected or other; dot screen means tone. Both are drawn in --primary12, or in the secondary's ink with patternInkSecondary for odd series slots, and every fill keeps a --primary12 outline. As CSS classes they suit legend swatches, meters and heat cells, and print through print-color-adjust: exact. Charts draw the same geometry as SVG patterns in their own defs. Text never sits directly on a semantic fill: put it on a knockout plate.
| Class | Geometry | Use |
|---|---|---|
patternHatch | 1 px lines at 45°, ≈ 5.7 px apart | Series 2 |
patternHatch0 | 1 px lines at 0°, 6 px apart | Series 4 |
patternHatchCrossed | 45° and 135° together | Series 5 |
patternHatch135 | 1 px lines at 135° | Projected or partial data only |
patternDotscreen | 1.25 px dots at 6 px | Tone, lightest step |
patternDotscreenPitch2 | 1.25 px dots at 5 px | Series 3 |
patternDotscreenPitch3 | 1.25 px dots at 4 px | Tone |
patternDotscreenHeavy | 2 px dots at 4 px | Tone, heaviest dot step |
patternHatch45°; series 2patternHatch00°; series 4patternHatchCrossedCrossed, secondary ink; series 5patternHatch135135°; projected or partialpatternDotscreen1.25 px at 6 pxpatternDotscreenPitch21.25 px at 5 px, secondary ink; series 3patternDotscreenPitch31.25 px at 4 pxpatternDotscreenHeavy2 px at 4 px
import patterns from '@fairgarden-private/design/utils/pattern.module.css'
import styles from './semantic.module.css'
const fills = [
{ className: patterns.patternHatch, name: 'patternHatch', note: '45°; series 2' },
{ className: patterns.patternHatch0, name: 'patternHatch0', note: '0°; series 4' },
{
className: `${patterns.patternHatchCrossed} ${patterns.patternInkSecondary}`,
name: 'patternHatchCrossed',
note: 'Crossed, secondary ink; series 5',
},
{ className: patterns.patternHatch135, name: 'patternHatch135', note: '135°; projected or partial' },
{ className: patterns.patternDotscreen, name: 'patternDotscreen', note: '1.25 px at 6 px' },
{
className: `${patterns.patternDotscreenPitch2} ${patterns.patternInkSecondary}`,
name: 'patternDotscreenPitch2',
note: '1.25 px at 5 px, secondary ink; series 3',
},
{ className: patterns.patternDotscreenPitch3, name: 'patternDotscreenPitch3', note: '1.25 px at 4 px' },
{ className: patterns.patternDotscreenHeavy, name: 'patternDotscreenHeavy', note: '2 px at 4 px' },
]
/**
* Semantic fills as legend swatches: each has a 1 px --primary12 outline
* and prints black through print-color-adjust: exact. Charts draw the same
* geometry as SVG patterns in their own defs.
*/
export function PatternSemantic() {
return (
<ul className={styles.legend}>
{fills.map(({ className, name, note }) => (
<li key={name} className={styles.item}>
<span className={`${styles.swatch} ${className}`} aria-hidden="true" />
<span className={styles.text}>
<code className={styles.name}>{name}</code>
<span className={styles.note}>{note}</span>
</span>
</li>
))}
</ul>
)
}
Trail and markers
Trail and markers
The ornament parts are inline SVG from utils/Ornament, styled by utils/ornament.module.css. OrnamentTrail is the signature dashed trail: a freehand line in the accent (the one ink on saturated fields) from an open circle to an open triangle. shape="short-tail" (default) turns down into its end, as in an empty state; shape="entry" loops once and runs level into a label, which sits 8 px past its end; flip mirrors either. Use one trail per band and two per page, never with an eyebrow rule, and hide it rather than let it touch type. Marker draws one of the path markers in the current color: origin ○, waypoint ×, terminal ▷ (with angle for its direction) and current ●, a data mark your legend defines; size="timeline" enlarges the origin and terminal to 8 px. MarkerIndex is the 24 px numbered circle keyed to a list beside a map or figure, and MarkerRank the muted rank numeral of a ranked list. OrnamentBlob is the blob mount: a 136 × 130 px hand-cut disc in the secondary's step 9 over a larger under-disc in step 7, turned 8° so it shows only thin crescents, holding a centered logo, block icon or stat numeral in the disc's contrast ink. Use one per page, on the page grounds and the night band; on forest and the solid fields it leaves the mark bare. The trail prints black at 0.75 pt with 3 / 2.5 pt dashes; markers print black; the blob's discs drop and its mark prints black.
import {
Marker,
MarkerIndex,
MarkerRank,
OrnamentBlob,
OrnamentTrail,
} from '@fairgarden-private/design/utils/Ornament'
import styles from './ornaments.module.css'
/** The trail (short tail and entry), the marker set (trail, timeline, index and rank) and the blob mount. */
export function PatternOrnaments() {
return (
<div className={styles.stack}>
<div className={styles.row}>
<OrnamentTrail />
<div className={styles.entry}>
<OrnamentTrail shape="entry" />
<span className={styles.label}>Get outside</span>
</div>
</div>
<div className={styles.row}>
<span className={styles.set}>
<Marker kind="origin" />
<Marker kind="waypoint" />
<Marker kind="terminal" angle={0} />
<span className={styles.name}>On a trail</span>
</span>
<span className={styles.set}>
<Marker kind="origin" size="timeline" />
<Marker kind="waypoint" size="timeline" />
<Marker kind="current" size="timeline" />
<Marker kind="terminal" size="timeline" />
<span className={styles.name}>On a timeline</span>
</span>
<span className={styles.set}>
<MarkerIndex value={1} />
<MarkerIndex value={2} />
<MarkerIndex value={12} />
<span className={styles.name}>Index</span>
</span>
<span className={styles.set}>
<MarkerRank value={1} />
<MarkerRank value={2} />
<MarkerRank value={10} />
<span className={styles.name}>Rank</span>
</span>
</div>
<div className={styles.row}>
<OrnamentBlob>
<span className={styles.numeral}>40</span>
</OrnamentBlob>
<span className={styles.name}>Blob mount with a stat numeral</span>
</div>
</div>
)
}
Rules
- Keep patterned area under 35% of the page above the footer, at every breakpoint.
- Two patterned bands never touch, and two patterns never overlap: put a solid band or container between them.
- Only the ground's main ink (
--primary12) sits on a decorative pattern. Muted text, accent text and required rules (--role-rule) go on a solid plate. - Pattern geometry never scales with the viewport or the container.
Full specification: DESIGN-SYSTEM.md §4.10 (patterns), §4.9 (shaped edges), §4.7 (the trail and markers), §6.6 (the blob mount), §1.5.9 (pattern tokens) and §1.5.16 (ornaments and markers); companion guide §I.