FairGarden Design

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.

newsletter-card.module.css
.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

paper

Field Notes

forest

Field Notes

amber
Figure 1. A specimen plate on the hairline grid.
PatternDotgrid.tsx
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

PatternSpeckle.tsx
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

PatternSunburst.tsx
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.

PatternGrain.tsx
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

PatternAwning.tsx
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

PatternFringe.tsx
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.

ClassGeometryUse
patternHatch1 px lines at 45°, ≈ 5.7 px apartSeries 2
patternHatch01 px lines at 0°, 6 px apartSeries 4
patternHatchCrossed45° and 135° togetherSeries 5
patternHatch1351 px lines at 135°Projected or partial data only
patternDotscreen1.25 px dots at 6 pxTone, lightest step
patternDotscreenPitch21.25 px dots at 5 pxSeries 3
patternDotscreenPitch31.25 px dots at 4 pxTone
patternDotscreenHeavy2 px dots at 4 pxTone, heaviest dot step
  • patternHatch45°; series 2
  • patternHatch00°; series 4
  • patternHatchCrossedCrossed, secondary ink; series 5
  • patternHatch135135°; projected or partial
  • patternDotscreen1.25 px at 6 px
  • patternDotscreenPitch21.25 px at 5 px, secondary ink; series 3
  • patternDotscreenPitch31.25 px at 4 px
  • patternDotscreenHeavy2 px at 4 px
PatternSemantic.tsx
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.

Get outside
On a trailOn a timeline010212Index1210Rank
40Blob mount with a stat numeral
PatternOrnaments.tsx
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.