Skip to content

[refactor] Consolidate hand-rolled eyebrow labels into <Eyebrow>#2815

Draft
joshestein wants to merge 19 commits into
masterfrom
josh/2795-consolidate-eyebrow
Draft

[refactor] Consolidate hand-rolled eyebrow labels into <Eyebrow>#2815
joshestein wants to merge 19 commits into
masterfrom
josh/2795-consolidate-eyebrow

Conversation

@joshestein

@joshestein joshestein commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Closes #2795

What changed

The "uppercase + small + letter-spaced" label recipe was hand-rolled at ~26 call sites with drifting tracking (0.05–0.18em), weight (medium/semibold), and size (12–16px). This PR converts them all to <Eyebrow> from @bluedot/ui, so they now share one voice.

  • Eyebrow gains id + style pass-through: id for the aria-labelledby target in MyBlueDotSidebar; style for runtime colours (HeroSection category labels, Congratulations course accent, puzzles prize accent) that Tailwind classes can't express (arbitrary-value classes must be build-time literals).
  • Storybook: added Muted and DynamicAccent stories.
  • CertificateCard keeps its explicit sizes (text-size-xs/sm) — the certificate is a fixed-proportion artifact whose labels scale with the document, not the UI ramp.

Deliberately not converted

  • Heading-role eyebrows: the puzzles pages' <h2 className={EYEBROW_CLASS}> section headings, /courses "Projects" <h2>, and the CourseSchedule sidebar <H2> group labels. Each is its section's only heading; converting to Eyebrow's <p> would remove them from the document outline. Decision: leave as-is rather than add an as prop. They keep their current styling until the designer question below is settled.
  • Badges/chips (calendar-tile month labels, lander tag chips, program-page pills), Nav "NEW" tags, AnnouncementBanner — non-label uppercase, out of scope per the issue.
  • RoundGroup's inline lead-in label (mid-sentence span; a <p> would break the flow).

Open questions

  1. Canonical tracking: 0.04em (this PR) vs the ~0.12–0.18em the newer pages used
  2. Weight: medium (this PR) vs semibold (was the majority)
  3. Size: 12px only, or 14px allowed in larger contexts
  4. Sanctioned colour alternates

Whichever way these land, it's a one-line change to Eyebrow.tsx's class string. Note the puzzles page currently shows both voices (parked h2s at 0.18em, converted prize labels at 0.04em) until then.

Screenshots (after)

Homepage (ladder step labels, events, newsletter):

Puzzles winners (converted prize labels vs parked h2 eyebrows):

AGI Strategy lander ("Cohort based course" hero eyebrow):

/alumni ("More stories"):

Rapid grants mobile (stats strip + step labels):

Testing

  • Website suite: 999/999 after updating 8 snapshots (diffs are exactly the class swaps + one span→p)
  • Website typecheck clean; libraries/ui typecheck failures are pre-existing (Footer/IconButton stories, Login.test — none touch Eyebrow)
  • Viewport check at 390px and 1440px on the 5 pages above: no horizontal scroll, no layout breakage
  • Auth-gated surfaces (course unit page, my-courses, congratulations, facilitator-feedback) covered by snapshots only — worth a staging click-through

🤖 Generated with Claude Code

joshestein and others added 19 commits July 17, 2026 13:22
Replace ~100 raw heading tags across website, speed-review, availability,
room and @bluedot/ui with the H1-H4 components so the consolidated
defaults (semibold, leading/tracking tokens, navy) come from one place.
Headings keep only semantic overrides: on-dark colors, genuinely smaller
size roles, and layout classes. Dissolves the bluedot-section-heading
shadow default into bare H3 and updates the stale text-component cheat
sheet in apps/website/CLAUDE.md.

Skipped: prose/markdown-rendered contexts (not-prose would break them),
eyebrow-recipe headings (deferred to eyebrow consolidation issue), and
the bespoke biosechackathon hero.

Part of #2735.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collapse ~196 arbitrary leading-[...]/tracking-[...] values across the
website and @bluedot/ui onto the 4-step token scales (leading tight
1.1 / snug 1.25 / normal 1.4 / relaxed 1.6; tracking tighter -0.02em /
tight -0.01em / normal 0 / wide 0.04em). px values converted to ratios
at their element's font size; sub-perceptual tracking (|x| <= 0.005em)
deleted; classes duplicating component defaults deleted.

31 values intentionally remain: uppercase eyebrow tracking (>= 0.06em,
deferred to the eyebrow consolidation issue), the bespoke
biosechackathon page, and leadings used as fixed-height layout
alignment. Full table in the PR description.

Part of #2735.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
H1/H2 bake in InterDisplay, but the pre-sweep raw tags at these call
sites rendered Inter. Downsized headings (<= text-size-lg) are not
display type, so restore Inter via font-sans; the utility layer beats
the component's base-layer font-family. Full-size and already-
InterDisplay usages (404 hero) unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-existing H1-H4 component call sites still carried classes written
against the old defaults (bold weights, explicit navy, re-stated
default sizes/leadings). Now that the components bake these in, the
repeats delete; near-default weight drift (font-bold on H1/H2 section
titles) normalizes to semibold.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DropoutModal and FacilitatorSwitchModal rendered each numbered form
section title as an H1 at body size - multiple h1s per page and a
misuse of the page-title component. These are labels, not headings;
use plain styled paragraphs, matching GroupSwitchModal's numbered
section pattern. The modals' actual titles stay with the Modal
wrapper's ModalTitle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per the typography spec, marketing heroes use the display ramp
(40/56/64/72) and text-size-xl belongs to H2. HeroH1 now composes that
ramp from existing tokens (2xl lg:3xl xl:4xl - each steps up once at
md, so no new token is needed) instead of defaulting to xl. The
homepage's inline px ramp and the lander HeroSection's deferred
48px-capped ramps delete; landers keep their weight/alignment/color
choices. The external course page title drops its size override and
uses the H1 default.

Homepage note: the first size step moves from 680px to 768px
viewports; the 680-767px band now renders 40px instead of 56px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The homepage display-treatment h2s (CourseValueProps, MergedLadder
rungs, StorySection, EventsSection) upsized to text-size-2xl, which
made them the same 56px as the hero in the 768-1023px band - the
H1/H2 collapse the typography spec bumped H1 to avoid. They now stay
on the standard xl ramp (32-48px); the display voice (weight 500,
tracking-tighter, ss04) is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
text-[32px] -> text-size-xl, text-[40px] -> text-size-2xl, and
text-[14px] -> text-size-xs where the arbitrary value exactly matches
an existing token. Drop text-[32px] entirely on the Congratulations H2
(it equals the H2 default). Gap-zone sizes (28px, 36px, 10px) are left
as-is. Congratulations button recipes now match CTALinkOrButton's
text-size-xs, which tightens their line-height from inherited 1.5 to
the token's 1.2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
text-size-xl/2xl jump to 48/56px at the md breakpoint, so five sweep
conversions that treated them as constant 32/40px silently grew on
desktop: QuoteSection short quotes (28->48px at xl), the EventsSection
date badge (sized to fit fixed-height containers), CourseCompletionSection
and Congratulations H2s, and the course-unit H1. Restore the fixed px
values (with their documenting eslint-disables) while keeping the
weight/leading/tracking normalizations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Converts 26 uppercase label call sites across 16 files to the Eyebrow
component, snapping size/weight/tracking to its defaults (text-size-xxs
/ font-medium / tracking-wide). Colours stay as per-site className or
style overrides. Eyebrow gains id + style pass-through for the
aria-labelledby target in MyBlueDotSidebar and the runtime course/accent
colours in HeroSection, Congratulations, and the puzzles page.

CertificateCard keeps its explicit sizes (the certificate is a
fixed-proportion artifact). Heading-role eyebrows (puzzles h2s,
/courses Projects, CourseSchedule sidebar), badges/pills, and
AnnouncementBanner are deliberately not converted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0643ccf2-b770-4e6b-856d-b585b45fac85

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch josh/2795-consolidate-eyebrow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@joshestein joshestein changed the title [refactor] Consolidate hand-rolled eyebrow labels into <Eyebrow> (#2795) [refactor] Consolidate hand-rolled eyebrow labels into <Eyebrow> Jul 21, 2026
@joshestein
joshestein changed the base branch from master to josh/2753-sweep-raw-components July 21, 2026 13:06
@joshestein

Copy link
Copy Markdown
Collaborator Author

@greptile-apps review

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR consolidates website eyebrow labels onto the shared Eyebrow component. The main changes are:

  • Replaces hand-rolled label styles across website pages and components.
  • Adds id and inline style forwarding for accessibility labels and dynamic colors.
  • Preserves explicit certificate label sizes through caller overrides.
  • Adds muted and dynamic-accent Storybook examples.
  • Updates affected snapshots for the shared typography.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The new props are optional and correctly forwarded.
  • Dynamic colors and accessibility labeling remain intact.
  • Converted elements remain in valid flow-content containers.
  • Caller typography overrides are preserved by the shared class-merging utility.

Important Files Changed

Filename Overview
libraries/ui/src/Eyebrow.tsx Adds optional id and style props and forwards them to the rendered paragraph.
libraries/ui/src/Eyebrow.stories.tsx Adds examples for muted text and runtime accent colors.
apps/website/src/components/lander/components/HeroSection.tsx Moves four category-label variants to Eyebrow while preserving computed colors.
apps/website/src/components/certificate/CertificateCard.tsx Consolidates certificate labels while retaining their explicit size, spacing, alignment, and color overrides.
apps/website/src/components/my-bluedot/MyBlueDotSidebar.tsx Uses Eyebrow for the navigation label while preserving the ID referenced by aria-labelledby.
apps/website/src/components/homepage/NewsletterBanner.tsx Replaces the standalone newsletter label with the shared paragraph-based component.
apps/website/src/components/courses/UnitLayout.tsx Moves the course-unit label from the general paragraph component to canonical eyebrow typography.

Reviews (1): Last reviewed commit: "[chore] Remove PR screenshots after embe..." | Re-trigger Greptile

Base automatically changed from josh/2753-sweep-raw-components to master July 23, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants