Skip to content

OpenConceptLab/ocl_issues#2969 | Filled buttons keep 4.5:1 text contrast on hover - #117

Merged
paynejd merged 1 commit into
mainfrom
OpenConceptLab/ocl_issues#2969-dark-tones
Oct 27, 2026
Merged

paynejd merged 1 commit into
mainfrom
OpenConceptLab/ocl_issues#2969-dark-tones

Conversation

@paynejd

@paynejd paynejd commented Oct 27, 2026

Copy link
Copy Markdown
Member

MUI fills a hovered contained button, and a hovered or keyboard-focused clickable chip that has a color, with palette.<color>.dark under contrastText (white). Every .dark in src/common/colors.jsx was a tone-80 light tint, so that text fell to about 1.7:1. WCAG 1.4.3 asks 4.5:1.

What changed

  • src/common/colors.jsx: each .dark is now its palette's tone 30, with a comment on what dark is for.

    Palette Old .dark New .dark White text on it
    primary #c3c0ff #2d00e5 9.38:1
    secondary #c7c4dd #464559 9.29:1
    tertiary #eab9d1 #603c50 9.29:1
    error #ffb4ab #93000a (M3 error tone 30) 9.38:1

    The theme in src/index.jsx spreads primary into success and secondary into info, so those follow. surface.dark is a text color (#1c1b1f) and stays.

  • src/components/users/UserMenu.jsx: the sign-out button used primary.dark as a light fill. It now names primary.80, the same #c3c0ff.

  • src/common/__tests__/colors.test.js: fails if primary, secondary, tertiary or error .dark drops under 4.5:1 against its contrastText (MUI's getContrastRatio).

Every .dark use in src/

  • primary.dark as the sign-out fill: moved to primary.80. It looks as before.
  • info.dark in CloseIconButton and DisplayMenu: never resolved, left alone (below).
  • surface.dark, about 30 text colors: the value doesn't change.
  • No .dark in SCSS, and no PRIMARY_COLORS.dark, COLORS.x.dark or palette.x.dark in code.

The info.dark finding

CloseIconButton passes color='info.dark' to IconButton. DisplayMenu passes it to ButtonGroup, to the unselected Button and to both icons. MUI 9's IconButton, Button, ButtonGroup and SvgIcon style the color prop only through variants built from palette names (Object.entries(theme.palette)). So 'info.dark' adds a colorInfo.dark class and no style.

Measured in headless Chromium under the app's palette, with the old and the new colors:

  • CloseIconButton: rgba(0, 0, 0, 0.54), IconButton's default (action.active). color='info' would give #5e5c71.
  • DisplayMenu, unselected button: text and icon inherit the surrounding text color, and the border is currentColor.

Both are identical before and after, so this PR leaves them alone.

One visible change to know about

MUI draws the line between the halves of a contained ButtonGroup in palette[color].dark. In oclmap that's MapButton's "Map ▾" split button before a concept is mapped. The line goes from #c3c0ff to #2d00e5, MUI's intended dark divider. Its contrast with the #4836ff fill drops from 3.80:1 to 1.45:1. The ▾ glyph still marks the menu half. An sx border color on that ButtonGroup would bring back the light line if we want it.

How it was tested

  • Test first: the new test failed on the old values (four failures, 1.70 to 1.71:1) and passes on the new ones.

  • Rendered check: a throwaway webpack harness (not committed) rendered the real CloseIconButton, DisplayMenu and common/Button under the app's palette, old and new colors side by side. Computed styles were read in headless Chromium after MUI's transition:

    Element Before After
    Contained primary button, hover #c3c0ff #2d00e5
    Contained error button, hover #ffb4ab #93000a
    Primary pill (common/Button, color='primary'), hover #c3c0ff #2d00e5
    Primary pill, keyboard focus (Mui-focusVisible) #c3c0ff #2d00e5
    Sign-out, rest / hover #c3c0ff / rgba(0,0,0,.12) same
    CloseIconButton rgba(0,0,0,.54) same
    DisplayMenu buttons and icons #4836ff selected, inherited otherwise same
    Contained ButtonGroup divider #c3c0ff #2d00e5
  • CI gate, as .github/workflows/pr.yml runs it: npm ci; ./node_modules/eslint/bin/eslint.js src --ext .jsx,.js exits 0; npm run test:all passes 607/607 unit and 6/6 integration tests.

Acceptance criteria covered here (oclmap)

  • The four .dark values in src/common/colors.jsx are tone 30, and nothing uses a .dark color as a light tint.
  • A test fails if any palette's .dark drops under 4.5:1 against its contrastText.
  • Filled primary and error buttons keep at least 4.5:1 on hover (9.38:1), and so does a primary pill on hover and keyboard focus. oclmap has no primary-colored pill today; common/Button with color='primary' was checked.
  • The sign-out button looks as before. The "Take a tour" chip is in TBv3, not here.

oclweb3 and the design system are separate PRs.

Part of OpenConceptLab/ocl_issues#2969

🤖 Generated with Claude Code

…ast on hover

MUI fills a hovered contained button, and a hovered or keyboard-focused
clickable chip that has a color, with palette.<color>.dark under
contrastText (white). Every .dark in colors.jsx was a tone-80 light tint,
so that text fell to about 1.7:1; WCAG 1.4.3 asks 4.5:1.

Each .dark is now its palette's tone 30: primary #2d00e5, secondary
#464559, tertiary #603c50, error #93000a (M3 error tone 30), each 9.29:1
or more under white. success and info spread primary and secondary in the
theme, so they follow. surface.dark is a text color and stays.

The user menu's sign-out button used primary.dark as a light fill; it now
names primary.80, the same #c3c0ff, so it looks as before.

CloseIconButton and DisplayMenu pass 'info.dark' as a color prop. MUI 9
matches that prop only against palette names, so it never resolved: they
render the default or inherited color, the same before and after.

A test fails if primary, secondary, tertiary or error .dark drops under
4.5:1 against its contrastText.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@paynejd paynejd left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review, pass 1 (codex-cli 0.164.0, commit 1356f90)

Verdict: clean. No material defects.

findings.json
{
  "reviewer": "codex-cli 0.164.0",
  "perspective": "defects",
  "mode": "full",
  "commit": "1356f902737be6380efe9af416e5bbcd6dfa7038",
  "verdict": "clean",
  "prior": [],
  "findings": []
}

@paynejd

paynejd commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

Simplification review (fresh Claude subagent, lean-loop simplify.md, commit 1356f90): verdict clean, nothing to cut. No commits after the Codex-reviewed commit, so no final Codex check was needed.

@paynejd

paynejd commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

On the MapButton divider: no caller reaches the contained split group today, so nothing on screen changes. MapButton has six call sites. Five pass simple, which renders a single Button. The sixth, MappingDecisionResult, passes isMapped mapOnly, which also renders a single Button. The ButtonGroup branch would draw MUI's divider in primary.dark, now a dark line, which is MUI's own default look. No change needed here.

@paynejd
paynejd merged commit cce87c4 into main Oct 27, 2026
2 checks passed
@paynejd
paynejd deleted the OpenConceptLab/ocl_issues#2969-dark-tones branch October 27, 2026 19:39
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