Repository navigation
Make positive and destructive buttons and yellow status text readable (WCAG AA) - #1873
Draft
dawsontoth wants to merge 1 commit into
Draft
dawsontoth wants to merge 1 commit into
dawsontoth wants to merge 1 commit into
Conversation
… readable White text on the positive button's green fill measured 2.13:1 and on the destructive fill 3.73:1, in both themes since the text sits on the fill. Yellow chip and status text was 1.84:1 to 1.97:1 in light mode. All fail WCAG AA's 4.5:1. The positive button keeps its green fill and takes dark text (8.78:1), the remedy the warning button already uses. The destructive button and badge move to red-600 with white text (4.73:1), darkening to red-700 on hover (6.37:1) instead of the lightening /90 hover, which would drop below AA in light mode. Yellow text in the cluster status and safe-mode pills, the usage "Cycle exhausted" chip and meter, the API explorer's PUT and 4xx badges and the CORS-disabled notice is amber-800 in light mode (6.13:1 or better) and stays yellow in dark mode, as ClusterCard already does. Closes #1859 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Code Review
This pull request improves color contrast and accessibility (WCAG AA compliance) across various UI components. It replaces low-contrast white text on green and destructive backgrounds with higher-contrast alternatives, such as dark text on green and bg-red-600 for destructive elements. It also updates yellow text on light backgrounds to use text-amber-800 in light mode while retaining dark:text-yellow for dark mode. Corresponding unit tests have been added and updated to verify these style changes. There are no review comments, and I have no additional feedback to provide.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⊙ Problem
Two shared
Buttonvariants and the hand-rolled yellow status text failed WCAG AA (4.5:1 for normal-size text).Button variant="positive"put white text on--green(2.13:1), andvariant="destructive"put white text on--destructive(3.73:1). Its/90hover lightened the fill on light surfaces, which made it worse (3.36:1). Yellow chip and status text was--yellowon its own tint or on a white card (1.71:1 to 1.97:1). The issue says dark mode is fine, and that holds for the yellow text, but not for the buttons: their text sits on the fill rather than on the page, so both buttons failed in both themes. Closes #1859.💡 Solution
--greenfill and switches to dark text (text-black-dark), which is 8.78:1. This is the remedy fix(ui): make the warning Alert and Button readable in light mode #1860 used for the warning button (black on yellow).red-600with white text, 4.73:1. Hover now darkens tored-700(6.37:1) instead of lightening with/90.red-600(#e7000b) is a more saturated version of the old#ef4444, so it still reads as plain red. White text stays because that is the convention for a destructive action, andred-600is the lightest Tailwind red that clears 4.5:1 for white.amber-800in light mode and stays--yellowin dark mode (text-amber-800 dark:text-yellow), asClusterCardalready does.amber-700, the shadeClustersListuses, isn't enough here: the cluster status pills sit on the lavender page, where it drops to 4.36:1.Final ratios, measured in Chrome from the repo's own Tailwind build (details under Verification). AA is 4.5:1 for all of these.
ButtonpositiveButtonpositive, hoverButtonandBadgedestructiveButtonandBadgedestructive, hoverbg-yellow/10The "before" column is on the card, or the darkest-margin surface where they differ. The button rows don't depend on the surface, because the text sits on an opaque fill.
⚖️ Alternatives
green-700gives 4.90:1 at rest, but its/90hover is 4.13:1 on a white card, and the button would no longer be the brand green thatpositiveOutline, the badges and the usage meter use.--destructive. That is 5.02:1 and keeps the token, but black on red reads like a warning rather than a destructive action, and it has less margin thanred-600with a darker hover.red-700with the/90hover. That is 6.37:1 at rest and at least 5.79:1 on hover, but the fill is visibly darker than today's red.red-600stays closer to the current color.text-red(issue table: 3.56:1), left unchanged. Its only red text is theCardTitleattext-2xl, which is 24px and counts as large text under WCAG, so the bar is 3:1. It measures 3.56:1 on the light card and 3.09:1 on the dark card. Normal-size text inside it isCardDescription, which usestext-muted-foreground. The CORS notice overrides the color to yellow, which was 1.97:1 and failed even the large-text bar, so it now usestext-amber-800 dark:text-yellow(7.09:1).🔧 Changes
src/components/ui/buttonVariants.tsx:destructiveandpositive, linked above.src/components/ui/badgeVariants.tsx:destructive(also fixed). It had the same white-on---destructivepairing, used by the notification count and the unavailable-instance status.ClusterHome.tsx, the usage page's "Cycle exhausted" chip, the API explorer's PUT method badge and 4xx status badge, and the CORS-disabled notice inAPIDocs.tsx(linked above).UsageMeter.tsxis also fixed: its warning percentage wastext-yellowon the card too, but it wasn't in the issue's table. TheMethodBadgecomment now says why PUT's light-mode text isn't the palette token.✅ Verification
Route: class-level unit tests, plus a rendered measurement against the real Tailwind build. Styling needs no backend, but every caller sits behind a signed-in cluster view, so I didn't drive the app.
src/components/ui/filledVariants.test.tsx(new, jsdom) renders the real<Button variant="positive">, and the destructiveButtonandBadge. It asserts the fill and text classes, thered-700hover, and that nobg-destructive(orhover:bg-destructive/90) class remains.StatusBadge.test.tsand the newMethodBadge.test.tsxasserttext-amber-800plusdark:text-yellow, and no baretext-yellow, for 4xx and PUT. The 4xx boundary assertions now key on thebg-yellow/10tint. Fails-on-base: all five new assertions fail withclaude/1839-warning-variant-contrast's source.src/index.csswith the repo's@tailwindcss/node4.3.3 for the exact before and after class lists, laid each one out on the light card, page and popover and on the dark card, page and popover, and loaded the page in Chrome 152. The page painted each element's computed background chain and text color onto a canvas, so Chrome did its own color conversion and compositing, and computed the WCAG ratio from those pixels. That produced the table above, and it agrees with an independent oklch-to-sRGB calculation to within 0.04. The same check confirmed that the build emitsdark:text-yellowand that the text color under.darkis#ffa500. Visually, the positive button is black on green, the destructive button is white on a plain red, and the yellow chips read as amber on a yellow tint.npx vitest runoversrc/components,src/features/instance/apisandsrc/features/clusterpassed 37 files and 379 tests.npx tsc -b,npx oxlint --format stylish .andnpx dprint checkall exited 0. The pre-commit hook's full suite passed: 398 files, 3,670 tests.git fetchcould not sign through the 1Password SSH agent, and the Harper domain leg failed on an expired Claude OAuth session, so I triaged the findings by hand. No defect was found, so there was no second round..tsxextensions on the new relative imports. Kept as they are: every import insrc/components/uiand the explorer is extensionless, which is this repo's convention.:rootand.dark.🤖 Generated with Claude Code (Anthropic Claude Opus); posted via @dawsontoth.
Related PRs: #1860 overlaps (stacked base; adjacent variant lines in buttonVariants.tsx)
Complexity: easy
Review-Coverage: authored=claude; ran=gemini,codex; blocked=cursor-composer(no-receipt),domain(auth); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=1; full=1 @ ff4371a
Review-Attention: read ~3m (raised: degraded review) @ ff4371a