Passer au contenu principal

Newsletter

Implemented

Newsletter signup molecule (ADR-069 / plan T5): first name, last name, email, Canadian postal code, a persona choice (buyer / organizer), optional interest options and a required consent checkbox. Ships ZERO hardcoded copy — every title, label, placeholder, CTA, success/error message and the consent string is a prop. The consent box is unticked by default (Law 25 / GDPR). Used in the storefront footer modal and dedicated landing blocks.

Categorymolecules
Sourcepatterns/molecules/newsletter.html
AI HintUse for grouped UI patterns. Combine atoms into functional units.

Newsletter signup molecule — first name, last name, email, Canadian postal code, persona choice, optional interests and an unticked consent box. Zero hardcoded copy: every string is a prop.

Newsletter

Form & Choice — Family 3

Multi-field signup block. The consent checkbox is unticked by default (Law 25 / GDPR) and every user-visible string — labels, placeholders, CTA, success and validation messages — is a consumer-supplied prop.

Stay in the loop

Get early access to new events and seasonal passes.

Tell me about
Privacy policy

Toggle to exercise the error path — the handler resolves { success: false, error: "…" } instead of { success: true }.

This component supports light and dark mode.

Status

Implemented — exported from the barrel. Newsletter ships the multi-field contract (first name, last name, email, Canadian postal code, persona, optional interests and an unticked consent box). The molecule ships zero hardcoded copy: every title, label, placeholder, CTA, success message, validation message and the consent string is a prop, so FR/EN is a consumer decision.

Props

Prop
Type
Default
Description
title*
string
—

Headline above the form; also the form's accessible name.

description
string
—

Optional supporting copy under the title.

labels*
NewsletterLabels
—

Field labels — { firstName, lastName, email, postalCode, persona, interests }.

placeholder
string
—

Email input placeholder.

ctaLabel*
string
—

Submit button label.

successLabel*
string
—

Copy announced with role="status" on success.

invalidEmailMessage*
string
—

Copy announced when the email fails the format check.

invalidPostalCodeMessage*
string
—

Copy announced when the postal code fails validatePostalCode.

consentRequiredMessage*
string
—

Copy announced when the consent box is unticked.

errorMessage*
string
—

Fallback copy when the handler rejects.

consentLabel*
string
—

Consent checkbox label (Law 25 / GDPR opt-in).

privacyLink
{ href: string; label: string }
—

Optional privacy-policy link next to the consent box.

personas*
NewsletterPersonaOption[]
—

Persona choices — buyer / organizer.

defaultPersona
'buyer' | 'organizer'
—

Initially selected persona (first option by default).

interests
NewsletterInterestOption[]
—

Optional interest options rendered as checkboxes.

postalCodeRequired
boolean
—

Require the Canadian postal code.

onSubscribe*
(values: NewsletterValues) => Promise<{ success: boolean; error?: string }>
—

Async submit handler (receives the trimmed payload).

validatePostalCode
(value: string) => boolean
—

Optional postal-code validator.

Accessibility

  • The form is a labelled landmark; the four fields, the persona choice and the consent box live inside it.
  • Email validation runs on submit; an invalid field gets aria-invalid="true" and the error is announced with role="alert".
  • Success announces with role="status", a rejected submission with role="alert"; the email input stays wired to the message through aria-describedby.
  • The consent checkbox is unticked by default — consent is never pre-granted (Law 25 / GDPR).
  • While the handler is pending the form is aria-busy and the submit button is disabled.
  • Every control meets the 44px tap minimum (--ep-tap-min) with a visible focus ring.

Localization

The demo below renders a French variant so the i18n contract is demonstrated, not just told: the same component, different props.

Restez informés

Recevez les nouveautés et les offres en avant-première.

Suivez
Politique de confidentialité

Family boundaries (Form & Choice — Family 3)

  • FormField (atom, implemented) — generic label+control wrapper. Newsletter composes Input / Select / Checkbox directly, so each field owns its label, error slot and aria-describedby wiring.
  • FormSection / FormLayout / FormRow (molecules, implemented) — dashboard form layout chrome. No overlap: Newsletter is a single-purpose public signup block.
  • Alert (molecule, implemented) — Newsletter's inline states reuse Alert's announcement semantics exactly (error → role="alert", others → role="status").
  • FooterStub (organism, implemented) — the footer's newsletter slot is the trigger that opens the modal hosting this molecule (ADR-065 / D17).
  • WaitlistJoinForm (molecule, planned) — near-identical UI shape, different job: an event-scoped capacity waitlist with a sync onJoin handler.

Props

Preview

Newsletter

Prop
Type
Required
Default
Description
title
string
Required
—

Headline above the form; also the form's accessible name.

description
string
Optional
—

Optional supporting copy under the title.

labels
NewsletterLabels
Required
—

Field labels — { firstName, lastName, email, postalCode, persona, interests }.

placeholder
string
Optional
—

Email input placeholder.

ctaLabel
string
Required
—

Submit button label.

successLabel
string
Required
—

Copy announced with role='status' after a successful subscription.

invalidEmailMessage
string
Required
—

Copy announced when the email fails the format check.

invalidPostalCodeMessage
string
Required
—

Copy announced when the postal code fails validatePostalCode.

consentRequiredMessage
string
Required
—

Copy announced when the consent box is still unticked on submit.

errorMessage
string
Required
—

Fallback copy when onSubscribe rejects or returns no error string.

consentLabel
string
Required
—

Consent checkbox label (Law 25 / GDPR opt-in).

privacyLink
{ href: string; label: string }
Optional
—

Optional privacy-policy link rendered next to the consent box.

personas
NewsletterPersonaOption[]
Required
—

Persona choices — [{ value: 'buyer' | 'organizer', label }]. The first entry is the default selection.

defaultPersona
'buyer' | 'organizer'
Optional
First persona option

Initially selected persona.

interests
NewsletterInterestOption[]
Optional
—

Optional interest options — [{ id, label, checked? }] rendered as a list of checkboxes.

postalCodeRequired
boolean
Optional
False

Require the Canadian postal code (the footer modal asks for it).

onSubscribe
(values: NewsletterValues) => Promise<{ success: boolean; error?: string }>
Required
—

Async submit handler. Receives the trimmed { firstName, lastName, email, postalCode, persona, interests } payload.

validatePostalCode
(value: string) => boolean
Optional
—

Optional postal-code validator; returning false rejects the value with invalidPostalCodeMessage.

className
string
Optional
—

Additional CSS classes on the form.

Quick use:<Newsletter />

Accessibility

ARIA roleform

Type

1 rule

NASA Rule 7

email must validate as RFC 5322 (NASA Rule 7)

ARIA

2 rules

WCAG 3.3.1, 4.1.3

Error has role='alert'; success has role='status'; input has aria-describedby

WCAG 3.3.1 / 4.1.3

Invalid fields carry aria-invalid='true'; the email input stays wired to its message with aria-describedby

behavior

3 rules

ADR-069 / D16

The consent checkbox is rendered unticked — consent is never pre-granted (Law 25 / GDPR)

ADR-069 / D16

The molecule ships zero hardcoded user-visible copy — every string is a prop

WCAG 2.5.5 / 2.4.7

Interactive controls meet the 44px tap minimum (--ep-tap-min) with visible focus rings

Usage

Import

tsxSnippet
import { Newsletter } from "@epasslive/design-system"

Example 1

tsxSnippet
<Newsletter title="Stay in the loop" labels={labels} ctaLabel="Subscribe" successLabel="Thanks!" invalidEmailMessage="Invalid email" invalidPostalCodeMessage="Invalid postal code" consentRequiredMessage="Consent required" errorMessage="Try again" consentLabel="I agree" personas={[{ value: 'buyer', label: 'Buyer' }]} onSubscribe={subscribe} />

Anti-patterns

— Do not hardcode data in molecules

— Avoid complex business logic in molecules

— Do not hardcode user-visible copy — pass it as props (bilingual by construction)

— Do not pre-tick the consent checkbox

Design System v4.1.0 | ePassLive