Skip to content

fix(compiler): map color: inherit to the inherited-color variable#391

Draft
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/color-inherit
Draft

fix(compiler): map color: inherit to the inherited-color variable#391
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/color-inherit

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

text-inherit (color: inherit) renders correctly on web but falls back to React Native's default text color on native — a dark surface with an inheriting label becomes black-on-black:

<View className="bg-primary text-primary-foreground">
  <Text className="text-inherit">Label</Text>  {/* correct on web, black-on-black on native */}
</View>

Cause — web keeps the CSS, native drops it

The two runtimes handle className differently, and only native breaks:

  • Webweb/api.tsx's useCssElement assigns { $$css: true, className } to style, so react-native-web applies the class name as a real CSS class. color: inherit is then resolved by the browser's own cascade — nothing is compiled.
  • Native — there is no CSS engine, so compiler/declarations.ts compiles each value to an RN style object. Its token/ident branch treats inherit as unsupported and drops it with a warning, so the <Text> gets no color at all and RN's default wins.

So this is a native-only compiler gap, and the fix is native-only.

Fix

The native runtime already carries the mechanism. Because React Native doesn't inherit color across elements, every color rule publishes --__rn-css-color down its subtree, and parseUnparsed already resolves currentcolor to it. color: inherit just needs the same path — and it isn't an approximation: per CSS Color 3 §4.4, "if the currentColor keyword is set on the color property itself, it is treated as color: inherit." Same value, same descriptor.

1. parseUnparsed — resolve the inherited-color keywords to that variable instead of dropping them. unset is included because it computes to inherit on inherited properties and color is inherited; matched case-insensitively so the currentColor React authors write (and INHERIT) also land here:

const keyword = value.toLowerCase();
if (
  keyword === "currentcolor" ||
  ((keyword === "inherit" || keyword === "unset") && property === "color")
) {
  return [{}, "var", "__rn-css-color"] as const;
}

2. parseUnparsedDeclaration — the publish guard compared against a stale "-css-color" literal that never matched the emitted "__rn-css-color", so an inheriting rule would republish a circular --__rn-css-color: var(--__rn-css-color) and break its own descendants:

- value[2] !== "-css-color"
+ value[2] !== "__rn-css-color"

Keyword coverage

value on color result
inherit, unset, currentcolor (any case) var(--__rn-css-color) — the inherited color
initial dropped (its own semantics — the platform default; a follow-up)
inherit on a non-color property dropped — no per-property inheritance context exists on native
revert / revert-layer unchanged — RN has no cascade-origin to revert to

Reproduction

import { compile } from "react-native-css/compiler";
compile(".child { color: inherit; }").stylesheet();
// before: {}                                              — declaration dropped, warning emitted
// after:  { s: [["child", [{ s: [1,1],
//            d: [[[{}, "var", "__rn-css-color"], "color", 1]], dv: 1 }]]] }   — resolves to the inherited color

Test plan

Compiler (src/__tests__/compiler/compiler.test.tsx):

  • color: inherit compiles to the __rn-css-color lookup instead of being dropped, with no self-referential v publish; inherit and currentcolor compile identically.
  • color: red still publishes --__rn-css-color; non-color inherit and initial still drop to {}.
  • unset, INHERIT, and currentColor (case-insensitive) compile identically to inherit; currentcolor resolves on a non-color property too (border-color).

Native render (src/__tests__/native/colors.test.tsx):

  • A child's color: inherit (and unset) resolves to the parent's color, on View and Text.
  • Inheritance chains through an intermediate inheriting node — this is what pins hunk 2: the middle node must not republish the circular variable, or the grandchild fails to resolve (the render test fails with only hunk 1 applied).
  • Corrects the vendor text-inherit test (vendor/tailwind/typography.test.tsx) that had encoded the dropped-declaration behaviour; it now resolves identically to text-current.

yarn test, yarn typecheck, yarn eslint, and prettier --check pass. (Two babel/ import-plugin suites fail only on Windows — a pre-existing path-separator bug in the plugin, unrelated to this change; green on the maintainer's CI.)

Tailwind's `text-inherit` (`color: inherit`) was dropped by the compiler and
rendered React Native's default text color (black on Android) instead of the
parent's color; web inherited correctly via real CSS.

lightningcss emits `color: inherit` as an UnparsedProperty (the keyword is not
a CssColor), and parseUnparsed's token/ident branch discarded it with a
warning. Per CSS Color 4, `currentcolor` used as the value of `color` is
defined as `inherit`, so both resolve to the --__rn-css-color variable every
color rule already publishes to its subtree -- no new machinery.

- parseUnparsed: resolve the inherited-color keywords -- `inherit`, `unset`
  (CSS Cascade: `unset` computes to `inherit` on the inherited `color`
  property), and `currentcolor` -- to the variable, matched case-insensitively
  (`currentColor`, `INHERIT`), instead of dropping them.
- parseUnparsedDeclaration: fix the self-reference guard, which compared against
  a stale "-css-color" literal that never matched the emitted "__rn-css-color".
  Without it an inheriting rule publishes a circular
  --__rn-css-color: var(--__rn-css-color), breaking resolution for its subtree.

`inherit` on non-color properties, and `initial`, remain dropped with a warning
(no per-property inheritance context exists). Adds compiler + native-render
tests, including the parent -> child -> grandchild chain that proves the
self-reference fix, and corrects the vendor text-inherit test that had encoded
the dropped-declaration behaviour.
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