Skip to content

fix: examples/blog error messages are 2.91:1 in light mode #1142

Description

@vivek7405

Problem

examples/blog has two separate destructive-colour defects. One is live and user-facing.

1. Live: error messages are 2.91:1 in light mode.

The palette declares --destructive once, in the dark-default :root block:

examples/blog/app/layout.ts:161   --destructive:            oklch(0.7 0.19 25);

Neither light override redeclares it (:root[data-theme='light'] at L187, nor the @media (prefers-color-scheme: light) block at L210), but both DO set --card: oklch(1 0 0). So light mode paints a red chosen for a near-black surface onto a white card.

alertClass({ variant: 'destructive' }) is bg-card text-destructive … (examples/blog/components/ui/alert.ts:37), which puts that red text on the white card at 2.91:1, against the 4.5:1 minimum. It renders on real user paths:

  • examples/blog/modules/auth/components/auth-forms.ts:106, the failed-login message
  • examples/blog/modules/posts/components/new-post.ts:76, the failed-post-submit message

An error message is the worst possible place for low contrast, and a failed login is exactly when a user is already having trouble. Dark mode is fine (6.88:1); the bug is light-mode only, which is the usual way this escapes notice.

2. Dormant: the declared fill pair is 2.59:1.

examples/blog/app/layout.ts:162   --destructive-foreground: oklch(0.96 0 0);

Near-white text on that light red is 2.59:1. Nothing renders it today (nothing in the app uses a filled destructive variant, and the app's owned components/ui/button.ts:40 still carries the pre-#1138 text-white plus dark:bg-destructive/60), so this is a trap rather than a live failure. It springs on the first buttonClass({ variant: 'destructive' }), or on the next webjs ui add button, which would bring in the current kit source that honours text-destructive-foreground.

Both were found by a repo-wide sweep during #1138 / #1139, which fixed the same class of bug in the kit itself.

Design / approach

Defect 1 is the priority. Give light mode its own --destructive, deep enough to read on white, in BOTH light blocks (the forced [data-theme='light'] and the prefers-color-scheme: light default). The kit's own light value, oklch(0.52 0.20 25), measures 6.12:1 on white and is a reasonable starting point, though the blog has its own warmer palette so match it by eye and then verify.

Defect 2 is the same fix #1139 applied to the kit: keep the dark red (its live text-on-card role needs it light) and flip the dark foreground to a dark red so a filled variant would be legible.

Verify with the maths, not by eye. packages/ui/test/destructive-contrast.test.js has a reusable oklch-to-sRGB plus WCAG implementation, and its pairings() list is the set that matters here too.

Then decide whether to refresh examples/blog/components/ui/button.ts and badge.ts from the current kit. They are the shadcn "you own it" copies, so lagging is legitimate, but leaving them means the dogfood app demonstrates a pattern the kit no longer ships. webjsui diff shows the drift.

Implementation notes (for the implementing agent)

Where to edit:

  • examples/blog/app/layout.ts around L161-162 for the dark values, plus the two light blocks at ~L187 and ~L210, which currently inherit --destructive and must not.
  • Optionally examples/blog/components/ui/button.ts L39-40 and badge.ts, if refreshing the owned copies.

Landmines / gotchas:

  • The dark --destructive is load-bearing for the live error alerts in dark mode (6.88:1). Darkening it globally to suit white would fix light and break dark. This token genuinely needs a per-theme value, which is the whole reason the bug exists.
  • Check BOTH light blocks. Fixing only [data-theme='light'] leaves the OS-preference default broken, and that is the path most visitors take.
  • The blog compiles a static public/tailwind.css. Rebuild it. A dev server serving a stale stylesheet makes a colour change look like it did nothing, which cost real time on fix(ui): make the destructive red legible in dark mode #1139.
  • Do not verify by screenshot alone. 2.91:1 looks acceptable to most viewers, which is precisely why it shipped.

Invariants to respect:

  • Repo root AGENTS.md invariant 11 for any prose in comments.
  • packages/ui/AGENTS.md invariant 5 if the owned component copies are refreshed. Its list of deliberate divergences from shadcn is now recorded there.

Tests + docs surfaces:

  • examples/blog has smoke coverage under test/examples/. Consider whether the kit's contrast assertion should generalize to app palettes rather than being duplicated per app.
  • .agents/skills/webjs/references/styling.md now documents the fill-plus-foreground pairing rule and the per-theme flip, so the recipe this app diverged from is correct as of fix(ui): make the destructive red legible in dark mode #1139.

Acceptance criteria

  • The light-mode error alert clears 4.5:1 on both light paths (forced theme and OS preference), verified by computation
  • The dark-mode error alert still clears 4.5:1 (6.88:1 today, must not regress)
  • The declared --destructive / --destructive-foreground pair clears 4.5:1 in both themes
  • A filled destructive button and badge are legible in both themes
  • A decision is recorded on whether the owned button.ts / badge.ts copies are refreshed from the kit or deliberately left behind

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions