Repository navigation
Hero section, end to end #22
Description
Parent
Spec: #20
What to build
The first vertical slice through every layer: a visitor loads the site and sees a finished Hero — badge, headline with two coloured emphases, lead paragraph, two calls to action, and the trust points — rendered from tokens, in the Brand font, with every string from Messages.
This ticket establishes the Base component vocabulary that later sections build on, and proves the whole stack end to end.
The hero headline is the only rich text on the site and the only <br>. It is encoded as segments rather than HTML in JSON:
type Segment = string | { text: string; tone: 'brand' | 'success' } | { br: true };Base components take design decisions as variant props, never raw values. Primitives take a closed as union, never unrestricted. Heading takes level, so semantics and size stay independent.
Messages are accessed as a static typed import — no hook, no key strings, no codegen.
Acceptance criteria
- Base components:
Box,Text,Heading,Stack,Section,Prose,Link,Badge,Dot,VisuallyHidden,RichText -
BoxandTexttake closedasunions;as="marquee"does not typecheck -
Headingtakeslevel(1–6) independently of a size prop -
Linksupportsvariant="button";Buttonhas noasprop - Every Base component accepts
style?: StyleXStylesand spreadsstylex.props()itself - The Hero renders and matches the canvas at desktop and mobile widths
- The headline's two emphases render from the segment array, not from markup in JSON
- Every Hero string comes from Messages; no literal strings in the section
-
SectionacceptsidandlabelledByand rendersaria-labelledbypointing at its own heading - The prerendered HTML contains the full Hero text — verifiable with JavaScript disabled
- Lint passes with
jsx-no-literalsactive on the section
Blocked by
- Design system and brand font #21 — Design system and brand font
Activity
[-]Design system and brand font[/-][+]Hero section, end to end[/+]Abuubkar commentedon Sep 5, 2026
Done on branch
ticket/22-hero(88b30db). Twelve Base components and the Hero composed from them.Components
Box·Text·Heading·Stack·Section·Prose·Link·Badge·Dot·List/ListItem·VisuallyHidden·RichTextasunions.Boxtakes 9 element names,Texttakes 3.as="marquee"does not typecheck.Headingtakeslevelandsizeindependently, so an h3 can render large without lying about document structure.Buttonhas noas. Both hero CTAs areLink variant="button"andvariant="buttonSecondary"— the canvas's 6 brand-background anchors against 2 real buttons made this the common case, not the exception.Sectionowns padding, container width,scroll-margin-top, and — whentone="inverse"— the Dark theme.Two things the ticket did not anticipate
1. The hero emphasis needed a token that did not exist. #8 demoted teal600 to "large text and decoration only" but left no Role pointing at it, and the headline is exactly that case:
clamp(34px, 5.4vw, 64px)at 4.18:1, which passes AA for large text and fails for normal text. AddedtextAccentDisplaywith the constraint written into its comment — "Never use it below 24px." UsingtextLink(teal700) instead would have been safe but would have darkened the headline away from the canvas.2.
font-familyand the page background belong on<body>, not a component. Without them the hero rendered in the UA serif, and a browser in dark mode painted its own dark canvas below the hero — which looked like a theming bug and was not. Both now come from tokens applied to<body>in the root document, pluscolor-scheme: lightin global CSS so no element lacking a background can pick up a dark UA canvas.Verified in a browser
h1Satoshi Variable, weight 800,rgb(15,27,45)= ink900,<br>intactrgb(14,138,138)teal600 · "Before They Happen" →rgb(46,158,91)green600aria-labelledbyhero-hresolves to theh1idmainid="top",tabIndex="-1"— ready for the skip link in #23#assessmentand#servicesvp checkWith JavaScript disabled: the prerendered HTML contains the complete hero — badge, headline with both emphases and the
<br>, lead, both CTAs, and all five trust points.Rich text
The
Segmentencoding from #11 works as designed. One cast is needed and it lives in exactly one place:resolveJsonModulewidens the segment array to a structural type, somessages.tsnarrows it back withheroTitle as readonly Segment[]. No component takes a message key —RichTexttakes segments, everything else takes resolved strings as children.Notes for the tickets this unblocks
mainalready hasid="top"andtabIndex="-1", soSkipLinkonly needs the link itself.Card,Grid,IconandIconTileare the remaining tier-2 components;List/ListItemalready exist.ListandStackgap props share aSpaceStepunion, so any new gap value needs adding in both — worth folding into one shared map if a third component needs it.Against the #18 budget
CSS is now 9.72 kB raw / 3.20 kB gzip, against a ~5 kB budgeted target. That is the whole component vocabulary plus the Hero, and StyleX atomic classes deduplicate as sections are added, so the growth curve should flatten. Worth watching at #24, which adds the most components of any remaining ticket.
Abuubkar commentedon Sep 5, 2026
Done on branch
ticket/22-hero. Twelve Base components plus the Hero, verified in a browser and with JS disabled.Hero section, and the Base component vocabulary it establishes