Skip to content

--color-destructive-foreground is registered in @theme inline but --destructive-foreground is never declared in :root or .dark, so text-destructive-foreground silently inherits the parent's color #1240

Description

@rawdaymx

Summary

default.css's @theme inline block registers --color-destructive-foreground: var(--destructive-foreground); (line 74), which is what makes the text-destructive-foreground / bg-destructive text-destructive-foreground utility classes exist for Tailwind to generate. But --destructive-foreground itself is never assigned a value anywhere in :root (lines 80-113) or .dark (lines 115-147) — every other token registered in @theme inline (--destructive, --card, --primary, …) has a matching declaration in both blocks; this one does not.

The practical effect: any component author who writes text-destructive-foreground gets a CSS
custom property that resolves to nothing, so the browser falls back to color: inherit (or the
initial value, depending on cascade) rather than a color chosen to be legible against
--destructive. Two call sites in the current tree already do this
(prize-list-editor.tsx:143, sersavan/multi-select.tsx:58), both via bg-destructive text-destructive-foreground.

Control: the sibling token --destructive (also referenced from @theme inline, line 39) is
declared in both :root (line 96) and .dark (line 130) — so this is specifically the
-foreground counterpart that's missing, not a broader problem with the destructive palette.

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 confirming absence with git grep

How to see it

$ git grep -n "destructive-foreground" upstream/main -- packages/ui/src/styles/default.css
upstream/main:packages/ui/src/styles/default.css:74:  --color-destructive-foreground: var(--destructive-foreground);

That is the only hit in the file: line 74, inside @theme inline. Neither :root (lines
80-113) nor .dark (lines 115-147) contains a --destructive-foreground: declaration.

Two live usages that inherit whatever color was cascading in from a parent element instead of
an intentional value:

$ git grep -n "destructive-foreground" upstream/main -- 'apps/*.tsx' 'packages/*.tsx'
upstream/main:apps/builder/src/features/minigames/components/prize-list-editor.tsx:143:                "bg-destructive text-destructive-foreground hover:bg-destructive/90",
upstream/main:packages/ui/src/components/ui/sersavan/multi-select.tsx:58:        "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",

Evidence

The measurement script reports the token as missing, not as a numeric contrast failure — that
distinction (missing vs. failing) is itself the evidence:

$ pnpm exec tsx medir.ts default.css
claro [ ... 'claro/estado-fg-destructive falta destructive-foreground' ... ]
oscuro [ ... 'oscuro/estado-fg-destructive falta destructive-foreground' ... ]

By contrast, --destructive itself (the background half of the pair) resolves fine in both
modes and produces no "falta" (missing) row for the criteria that use only it (e.g.
destructivo-boton, error-borde).

Where it comes from

packages/ui/src/styles/default.css (commit 975c7a02a963d80bf140bb269d773b4912b8e7b6):
line 74 (@theme inline registration) vs. the absence of a matching declaration anywhere in
lines 80-147 (:root/.dark).

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

--color-destructive-foreground would not be registered in @theme inline at all if
destructive-foreground weren't meant to be a real, themed color — every other -foreground
counterpart in the same file (--primary-foreground, --secondary-foreground,
--accent-foreground, --card-foreground, --popover-foreground, --sidebar-*-foreground)
has both the @theme inline registration and a :root/.dark value. destructive-foreground
is the only one where the registration exists but the value doesn't — that asymmetry, present in
a file that otherwise pairs every registration with a declaration, reads as an omission rather
than a deliberate "no destructive text color" design choice.

Suggested fix

Values from a3-destructive.css (verified against the criteria above):

:root {
  --destructive-foreground: oklch(0.985 0 0); /* white */
}
.dark {
  --destructive-foreground: oklch(0.145 0 0); /* near-black */
}

White reads 4.89:1 against --destructive in light mode; the near-black ink reads 6.91:1
against --destructive in dark mode (white would only read 2.87:1 there — the two modes need
different foregrounds, which is exactly why this can't be fixed by hardcoding text-white at
the two call sites instead of fixing the token). Re-running the measurement script against the
patched file removes exactly the two "falta" rows and adds none:

$ pnpm exec tsx medir.ts a3-destructive.css
claro [ ...(same rows as before, minus 'claro/estado-fg-destructive falta destructive-foreground')... ]
oscuro [ ...(same rows as before, minus 'oscuro/estado-fg-destructive falta destructive-foreground')... ]

Workaround for anyone hitting this

The two current call sites (prize-list-editor.tsx:143, sersavan/multi-select.tsx:58) both
already pair bg-destructive with text-destructive-foreground; until the token is declared,
replacing text-destructive-foreground with a hardcoded text-white dark:text-[oklch(0.145_0_0)]
at each call site reproduces the intended contrast, at the cost of duplicating the value instead
of deriving it from the theme. Not verified beyond the two call sites listed above.

What we did not verify

  • Whether any other component (outside apps/**/*.tsx and packages/**/*.tsx, e.g. email
    templates) also references text-destructive-foreground.
  • The actual rendered color a browser falls back to in the two live call sites (we established
    the custom property resolves to nothing from the CSS source; we did not capture a live DOM
    computed-style screenshot of the fallback in a running app).

Related issues — none of these describes it

Searched gh search issues --repo ChatbotXIO/ChatbotX "destructive-foreground" (no --state
filter); 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