Skip to content

Fix white-on-red contrast: deepen the button fill to #d42f46 - #20

Merged
leowilkin merged 1 commit into
mainfrom
design/deepened-red-contrast
Aug 13, 2026
Merged

Fix white-on-red contrast: deepen the button fill to #d42f46#20
leowilkin merged 1 commit into
mainfrom
design/deepened-red-contrast

Conversation

@leowilkin

Copy link
Copy Markdown
Member

What

Hack Club red (#ec3750) measures 4.02:1 against white — below WCAG AA's 4.5:1 for normal text. WCAG's large-text exemption (3:1) starts at 18.66px bold or 24px regular, which no button in Attend reaches, so no type change fixes this. The fill has to move.

Wherever white text sits on a red surface, the resting fill is now --accent-strong (#d42f46, 4.89:1), hovering to #b82840 (6.15:1). The brand red keeps every use that carries no text on it: unread dots, the wallet-pass bar, the Turbo progress bar, borders, icon fills, and /10 tints. The two reds are one perceptual step apart — the change is invisible at a glance and measurable to a contrast checker.

112 fills across 84 view files.

Theming

themes.css maps the new utilities to the same semantic vars the old ones used:

  • .bg-[#d42f46]var(--accent)
  • .hover:bg-[#b82840]var(--accent-strong)

So the seven non-light themes render identically to before. Only the default light theme changes.

Two bugs fixed on the way

  • Three buttons in admin/events (index, show, _form) had bg-[#ec3750] hover:bg-[#ec3750] — a no-op hover with transition-colors doing nothing. They now have a real hover state.
  • Eight sites used hover:bg-[#d63146], a one-off hex themes.css never remapped, so they broke out of theming on hover. All now use the mapped token.

Docs

Adds PRODUCT.md and DESIGN.md recording the product context and the incumbent visual system, where this rule lives as The Deepened Red Rule, plus the .impeccable/design.json sidecar.

Verification

  • Contrast computed for every pair (numbers above)
  • Tailwind rebuilt; both new utilities confirmed present in the compiled CSS
  • All 84 changed ERB files parse
  • Design detector run over the diff: 47 findings, 0 touching the new fills — all pre-existing

Known gaps, deliberately out of scope

  • The decorative wallet-pass mock on home#index (10px text-white/80 on red, plus initials on a red→orange gradient at ~2.2:1) still fails. It's illustration, not interactive UI.
  • Dark theme resolves text-white to --bg-elev (#161922), giving 4.37:1 on --accent. Same class of problem, separate fix.

Hack Club red (#ec3750) measures 4.02:1 against white, below WCAG AA's
4.5:1 for normal text. WCAG's large-text exemption starts at 18.66px bold
or 24px regular, which no button in Attend reaches, so no type change can
fix this — the fill has to move.

Wherever white text sits on a red surface, the resting fill is now
--accent-strong (#d42f46, 4.89:1), hovering to #b82840 (6.15:1). The brand
red keeps every use that carries no text on it: unread dots, the wallet
pass bar, the Turbo progress bar, borders, icon fills, and /10 tints. The
two reds are one perceptual step apart.

themes.css maps the new utilities to the same semantic vars the old ones
used (.bg-[#d42f46] -> var(--accent), .hover:bg-[#b82840] ->
var(--accent-strong)), so the seven non-light themes render identically to
before. Only the default light theme changes.

Two bugs fixed on the way:
  - Three buttons in admin/events (index, show, _form) had
    `bg-[#ec3750] hover:bg-[#ec3750]` — a no-op hover with transition-colors
    doing nothing. They now have a real hover state.
  - Eight sites used `hover:bg-[#d63146]`, a one-off hex themes.css never
    remapped, so they broke out of theming on hover. All now use the
    mapped token.

Also adds PRODUCT.md and DESIGN.md, which record the product context and
the incumbent visual system this rule (The Deepened Red Rule) lives in.

Known gaps, deliberately out of scope:
  - The decorative wallet-pass mock on home#index (10px text-white/80 on
    red, plus initials on a red->orange gradient) still fails.
  - Dark theme resolves text-white to --bg-elev (#161922), giving 4.37:1
    on --accent. Same class of problem, separate fix.
@leowilkin
leowilkin merged commit b068374 into main Aug 13, 2026
13 checks passed
@leowilkin
leowilkin deleted the design/deepened-red-contrast branch August 13, 2026 17:19
leowilkin added a commit that referenced this pull request Aug 13, 2026
Both guardian surfaces — the invite wizard and the portal center — were
generic Tailwind blue/gray, wrapped in fully branded Hack Club chrome. For
most parents this is the only Attend screen they ever see, and it is where
they hand over their child's medical and emergency details. DESIGN.md names
this directly: the blue running through the onboarding wizard and guardian
portal is "drift from a Tailwind default, not a decision".

Adds a portal-* component layer in app/assets/tailwind/application.css.
Every rule resolves through the semantic tokens in themes.css per The No
Literal Hex Rule; there are no hexes in the templates. It follows the Two-
Radius Rule (6px controls, 8px containers), the Flat-By-Default Rule (border
and tonal step, no resting shadow), the Fourteen Rule, Full-Width-Then-Auto
on every primary action, and the documented button, input, and badge specs.

The Deepened Red Rule is applied in all eight themes rather than only light.
The rule names --accent-strong, but that token lightens in the dark themes
(#ff5670), where white on it is 3.08:1 — worse than the red it replaces. PR
 #20 avoids this because themes.css maps its .bg-[#d42f46] utility back to
--accent outside light; a CSS layer has no such escape. Deriving from
--accent lands on #d43248 in light, indistinguishable from the rule's
#d42f46, and clears AA everywhere. Hover lands on --accent-deep.

Flow and hierarchy:
  - The overview had three overlapping progress representations (bar,
    checklist, footer callout). One spine now: checklist, slim meter, single
    next-action CTA.
  - Step names now say whose data they hold: "Participant Info"/"Details" ->
    "Participant details"/"Your details", each with a one-line description.
  - step.html.erb had no position indicator. It has one now — a rail on
    desktop, a compact "step 2 of 4" meter below sm.

Correctness and accessibility:
  - ~40 travel-section controls had visible labels with no programmatic
    association, plus two colliding ids between the train and bus blocks.
  - Every consent form rendered checkboxes with a duplicate consent_agreed
    id, so clicking a label focused the wrong control.
  - Guardian fields had no autocomplete attributes at all.
  - white on --success is 1.86:1, so filled "done" markers deepen it;
    --text-muted is 3.16:1 on white, so secondary copy moved to --text-soft.
    Every token pair verified against WCAG AA in light and dark.
  - f.submit -> button, since themes.css's unlayered input rule makes a
    styled input[type=submit] impossible in non-light themes.

Removes five unreachable views (consents, details, emergency, _wizard_nav,
complete): no route, no action, and three called guardian_step_path, a route
helper that does not exist — they would have raised if anything rendered
them. Also drops two console.log calls and a javascript:history.back() link.

Adds shared/_portal_masthead: the app header only renders for signed-in
users, so guardians saw an unbranded form above a Hack Club footer.

Spec assertions updated for the new copy. Sentence case throughout, matching
the newer portal center; no factual claims changed.
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.

1 participant