Newsletter
Email capture, as the inline footer form, the royal straddle card, a band or a ruled row. A Base UI Form with a labelled email Field (and optionally a postcode and a consent Checkbox) and a submit Button; the result renders in place, in a polite live region. Never a pop-up, a multi-step flow or a modal.
import { Newsletter } from '@fairgarden-private/design/components/Newsletter'
The email label is visible ("Email Address"); a placeholder is an example only. onSubscribe receives the values once the form validates. While busy the form is inert and the submit reads "Sending…" at its rest width, with no spinner. status="success" replaces the form with the success glyph, "Subscribed" and statusMessage, and moves focus there; status="warning" reports a server failure above the submit. At most one newsletter module per page besides the footer block.
Inline, in the footer
Inline, in the footer
kind="inline" (default): the block-tier icon, the heading, the pitch and the field with the butted 64 × 48 px submit cell, which stays beside the field at every width. In a footer column it stays stacked, because it reflows on its own width.
'use client'
import * as React from 'react'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Link } from '@fairgarden-private/design/components/Link'
import {
Newsletter,
type NewsletterStatusKind,
} from '@fairgarden-private/design/components/Newsletter'
import styles from './inline.module.css'
/** An envelope at the block tier, drawn in currentColor (--role-accent). */
function Envelope() {
return (
<svg viewBox="0 0 36 36" aria-hidden="true" focusable="false" className={styles.icon}>
<rect x="4" y="8" width="28" height="20" rx="2" />
<path d="m5 10 13 10 13-10" />
</svg>
)
}
/** The inline footer form on the night band, with the butted submit cell. Submit to see "Sending…", then success. */
export function NewsletterInline() {
const [busy, setBusy] = React.useState(false)
const [status, setStatus] = React.useState<NewsletterStatusKind | undefined>()
return (
<Ground kind="band" preset="night" render={<div />} className={styles.band}>
<div className={styles.column}>
<Newsletter
heading="Subscribe"
pitch="Stories from the land, once a month."
icon={<Envelope />}
printUrl="example.org/newsletter"
busy={busy}
status={status}
statusMessage="Check your inbox to confirm."
legal={
<>
We never share your address. <Link href="https://example.org/privacy">Privacy</Link>
</>
}
onSubscribe={() => {
setBusy(true)
window.setTimeout(() => {
setBusy(false)
setStatus('success')
}, 1200)
}}
/>
</div>
</Ground>
)
}
Straddle card
Straddle card
kind="straddle" is a royal field, the page's one royal card, placed first in the band below (the footer's straddle slot, or any Ground band). It rises across that band's seam so the seam falls halfway between its heading and first field, however the heading wraps, and sits over both grounds with its one-ink edge; the lower band's content starts 56 px below it. It fills the column at base and is capped at 480 px, centered, from 768 px. With reading, on reference and editorial pages, from 1024 px it aligns to the reading column's start edge and widens to the reading measure, so Email and Postcode share a row. grained adds grain and sets the fields on white plates.
…and so the thrush returns each May to the same few acres of old forest, which is why protecting them matters.
The footer continues here.
'use client'
import * as React from 'react'
import { Ground } from '@fairgarden-private/design/components/Ground'
import { Link } from '@fairgarden-private/design/components/Link'
import { Newsletter } from '@fairgarden-private/design/components/Newsletter'
import styles from './straddle.module.css'
/**
* The royal straddle card opening the night band: the seam falls between
* its heading and first field. `reading` widens it to the reading measure
* from 1024 px, so Email and Postcode share a row.
*/
export function NewsletterStraddle() {
const [reading, setReading] = React.useState(true)
return (
<div className={styles.frame}>
<label className={styles.toggle}>
<input
type="checkbox"
checked={reading}
onChange={(event) => setReading(event.target.checked)}
/>{' '}
Reading page (align to the prose column)
</label>
<Ground kind="band" preset="paper" render={<div />} className={styles.upper}>
<p className={styles.prose}>
…and so the thrush returns each May to the same few acres of old forest, which is why
protecting them matters.
</p>
</Ground>
<Ground kind="band" preset="night" render={<div />} className={styles.lower}>
<div className={styles.container}>
<Newsletter
kind="straddle"
grained
reading={reading}
postcode
heading="Get the Latest Bird Conservation News"
printUrl="example.org/newsletter"
submitLabel="Sign Up"
legal={
<>
By signing up you agree to our <Link href="https://example.org/terms">terms</Link>.
</>
}
/>
<p className={styles.after}>The footer continues here.</p>
</div>
</Ground>
</div>
)
}
Band and ruled row
Band and ruled row
kind="band" sets a large headline and pitch beside the field and pill: they join from 480 px of its width and the band splits 6 + 6 from 768 px. field places it in a leaf or amber campaign field. kind="ruled" is the ruled footer row: a serif prompt cell beside the input and a square submit cell, stacked below 768 px.
import { Newsletter } from '@fairgarden-private/design/components/Newsletter'
import styles from './band.module.css'
/** The band on the page ground and in a leaf campaign field, and the ruled row. The ruled row shows a server failure. */
export function NewsletterBand() {
return (
<div className={styles.stack}>
<Newsletter
kind="band"
heading="Field Notes, Every Friday"
pitch="One story, one walk and one thing to do this weekend."
printUrl="example.org/notes"
/>
<Newsletter
kind="band"
field="leaf"
heading="Join the Spring Planting"
pitch="We'll send dates and places near you."
submitLabel="Count Me In"
printUrl="example.org/planting"
/>
<Newsletter
kind="ruled"
heading="Letters from the Orchard"
printUrl="example.org/letters"
status="warning"
statusMessage="We couldn't reach the server. Try again in a minute."
/>
</div>
)
}
In print
The form becomes one 0.75 pt box holding the heading and "Subscribe at printUrl"; fields, legal text, grain and icon drop, and a straddle card reflows in the flow with no seam. Inside the footer the form prints nothing; the footer's imprint carries the line.
API Reference
The newsletter signup. At most one per page besides the footer block; when a page carries one, the footer shows only a link. In print it becomes one 0.75 pt box: the heading and “Subscribe at “.
| Prop | Type | Description |
|---|---|---|
busy | | While the submission is pending: the form is inert and |
busyLabel | | The busy label, held at the rest width. Default “Sending…". |
consent | | Adds an optional consent Checkbox with this label. |
emailError | | The error shown when the email is missing or malformed. |
emailLabel | | The email field’s visible label. Default “Email Address” (title case [D160]). |
emailPlaceholder | | A placeholder: an example only, never the label; end it with “…". |
field | | |
grained | | |
heading | | The heading: |
headingLevel | | The heading level. Default |
icon | | The block-tier icon before the heading (an envelope), an |
kind | |
|
legal | | The legal or consent line, |
onSubscribe | | Called with the values once the form validates. |
pitch | | A one-line pitch, |
postcode | | Adds a postcode field ( |
primary | | Primary Radix scale: text, field edges and focus rings. Never defaulted [D133]. |
printUrl | | The short URL printed in place of the form: “Subscribe at example.org/newsletter” (no protocol). |
reading | | |
secondary | | Secondary Radix scale: the icon, band heading and link underlines. Never defaulted. |
status | | The result: |
statusMessage | | The one sentence that goes with the status. |
submitLabel | | The submit’s label (and the butted cell’s accessible name), title case, untracked. Default “Subscribe”. |
successTitle | | The status word for success. Default “Subscribed”. |
newsletter
NewsletterBandField
NewsletterProps
newsletterStatus
NewsletterStatusKind
NewsletterValues
Specification: DESIGN-SYSTEM.md §11.11 (newsletter signup; the royal field per D177, reading-page width per D187) and §5.6.2 (the straddle card).