Skip to content

Dark mode reuses the light-mode primary color unchanged (.dark { --primary: oklch(0.488 0.243 264.376) }): text-primary is 2.60:1 on --card and on --background in dark mode #1239

Description

@rawdaymx

Summary

default.css defines --primary once in :root (light) and defines it again in .dark with
the identical OKLCH value (oklch(0.488 0.243 264.376)). Every other themeable token in the
file (--background, --card, --muted-foreground, --border, …) gets a distinct, lighter
value in .dark to preserve contrast against the darker surfaces — --primary is the one
color token that doesn't. Against the actual dark-mode surfaces, that fixed blue computes to
2.60:1 on --card and 2.60:1 as a button border/background contrast case (boton-contra-fondo
criterion, i.e. the same primary tested as a filled control against --background) — both well
under WCAG's 4.5:1 (text) / 3:1 (non-text) floors depending on the usage. text-primary alone
appears in 73 files under apps/**/*.tsx and packages/**/*.tsx (measured below), so this
isn't a single stray usage.

The control that isolates this to --primary specifically: every other dark-mode color that
also changed value between :root and .dark (e.g. --foreground, --muted-foreground,
--accent-foreground) passes its corresponding contrast criterion in dark mode. Only the two
tokens upstream left byte-for-byte identical between modes (--primary here; see draft 03 for
--destructive-foreground, which is missing rather than identical) fail.

Environment

Defect type Static (code)
Commit 975c7a02a963d80bf140bb269d773b4912b8e7b6 (upstream/main)
How measured git fetch upstream && git rev-parse upstream/main, then reading packages/ui/src/styles/default.css at that commit and running the WCAG 2.x contrast math against its .dark values

How to see it

  1. packages/ui/src/styles/default.css, commit above:
    • :root line 88: --primary: oklch(0.488 0.243 264.376);
    • .dark line 122: --primary: oklch(0.488 0.243 264.376); — byte-identical to line 88
  2. Compute the WCAG 2.x contrast of --primary (as text, text-primary) against --card
    (oklch(0.21 0.006 285.885) in .dark) and against --background
    (oklab(18.288% 0.00014 -0.01275) in .dark).

Evidence

$ pnpm exec tsx medir.ts default.css
...
oscuro [
  'oscuro/enlace 2.60',
  'oscuro/boton-contra-fondo 2.60',
  'oscuro/anillo 1.45',
  'oscuro/lateral 3.39',
  ...
]

oscuro/enlace is text-primary (used as a link/accent text color) against --card;
oscuro/boton-contra-fondo is the same --primary value tested as a filled control's boundary
color against --background. Both read 2.60 — under 4.5:1 and under 3:1 respectively.

Usage count, text-primary as a standalone class (not text-primary-foreground), on
upstream/main at the commit above:

$ git grep -n -E "text-primary([^-]|$)" upstream/main -- 'apps/*.tsx' 'packages/*.tsx' | wc -l
73

(Witness: git grep -c -E "text-primary([^-]|$)" upstream/main -- packages/ui/src/components/ui/button.tsx1.)

Where it comes from

packages/ui/src/styles/default.css (commit 975c7a02a963d80bf140bb269d773b4912b8e7b6),
:root line 88 and .dark line 122.

Why we think this is a defect rather than the intended design

Every other surface-dependent color pair in the same file (--foreground/.dark --foreground,
--muted-foreground/.dark --muted-foreground, --accent-foreground/.dark --accent-foreground, etc.) is deliberately re-derived for the darker background — that is the
whole point of a .dark block existing at all. --primary sits alone as the one token that was
seemingly copy-pasted rather than re-derived. We found no comment or test asserting that the
brand's primary blue is meant to look identical, and therefore proportionally lower-contrast, in
dark mode.

Suggested fix

Values from a2-primario.css (verified against the criteria above):

.dark {
  --primary: oklch(0.707 0.165 254.624); /* Tailwind blue-400 */
  --primary-foreground: oklch(0.21 0.006 285.885);
}

oklch(0.707 0.165 254.624) reads 6.79:1 against --card and 7.17:1 against --background in
dark mode — comfortably above both floors. Re-running the measurement script against the
patched file removes exactly the two affected rows and adds none:

$ pnpm exec tsx medir.ts a2-primario.css
oscuro [
  'oscuro/anillo 1.45',
  'oscuro/lateral 3.39',
  ...
]

(oscuro/enlace and oscuro/boton-contra-fondo are gone; no oscuro/boton-texto row appears,
confirming the new value doesn't break the reverse case — primary-foreground-on-primary as a
filled button's text.)

What we did not verify

  • Whether blue-400 specifically is the right hue for this project's brand — any sufficiently
    light, sufficiently chromatic blue in the same hue family would clear the floor; we picked the
    nearest standard Tailwind stop for a concrete, buildable suggestion.
  • Any place outside apps/**/*.tsx and packages/**/*.tsx (e.g. email templates, marketing
    pages) that might also reference --primary and were not counted.
  • The APCA/WCAG 3 numbers for this pair — only WCAG 2.x is measured here.

Related issues — none of these describes it

Searched gh search issues --repo ChatbotXIO/ChatbotX "<term>" for dark mode primary and
primary contrast dark (no --state filter); both returned zero results. Witness that the
mechanism works: the same command with "bug" returns real issues (e.g. #1226, #1228).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions