Skip to content

Conversation

@logonoff
Copy link
Member

@logonoff logonoff commented Oct 1, 2025

Closes #11803

Enhanced the @patternfly/react-tokens package to include dark theme values in semantic token definitions, making it easier for developers to access both light and dark theme values programmatically.

Changes:

  • Added getDarkThemeDeclarations() to extract dark theme CSS rules
  • Added getDarkLocalVarsMap() to build dark theme variable mappings
  • Updated token generation to include darkValue and darkValues properties
  • Enhanced variable resolution to support dark theme context
  • Updated legacy token support to include dark values

Result:

  • 1,998 tokens now include dark theme values
  • Tokens with dark overrides expose darkValue property
  • Backward compatible with existing code
  • Enables programmatic theme switching and consistency

Implements: #11803

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Dark theme token variants are now supported alongside light themes in the token generation system.
  • Chores

    • Enhanced variable resolution to properly handle dark theme overrides across CSS files and dependencies.
    • Updated token export process to include dark theme values in legacy token compatibility paths.

✏️ Tip: You can customize this high-level summary in your review settings.

@rebeccaalpert
Copy link
Member

Seeing some build errors here:

Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(164,3): error TS2322: Type '{ style: { data: { fill: "var(--pf-v6-chart-area--data--Fill, --pf-t--chart--global--fill--color--900)"; fillOpacity: 0.3; strokeWidth: "--pf-t--chart--global--stroke--width--sm"; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--layout--height"; width: "--pf-...' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; } & VictoryCommonThemeProps & VictoryDatableProps'.
  Type '{ style: { data: { fill: "var(--pf-v6-chart-area--data--Fill, --pf-t--chart--global--fill--color--900)"; fillOpacity: 0.3; strokeWidth: "--pf-t--chart--global--stroke--width--sm"; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--layout--height"; width: "--pf-...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(177,3): error TS2322: Type '{ style: { axis: { fill: "var(--pf-v6-chart-axis--axis--Fill, transparent)"; strokeWidth: "--pf-t--chart--global--stroke--width--xs"; stroke: "var(--pf-v6-chart-axis--axis--stroke--Color, --pf-t--chart--global--fill--color--300)"; strokeLinecap: "--pf-t--chart--global--stroke-line-cap"; strokeLinejoin: "--pf-t--char...' is not assignable to type '{ style?: { axis?: VictoryStyleObject; axisLabel?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; grid?: VictoryStyleObject; ticks?: VictoryTickStyleObject; tickLabels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; offsetX?: number; offsetY?: number; } & VictoryCommonThemeProps'.
  Type '{ style: { axis: { fill: "var(--pf-v6-chart-axis--axis--Fill, transparent)"; strokeWidth: "--pf-t--chart--global--stroke--width--xs"; stroke: "var(--pf-v6-chart-axis--axis--stroke--Color, --pf-t--chart--global--fill--color--300)"; strokeLinecap: "--pf-t--chart--global--stroke-line-cap"; strokeLinejoin: "--pf-t--char...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(226,3): error TS2322: Type '{ style: { max: { padding: 8; stroke: "var(--pf-v6-chart-boxplot--max--stroke--Color, --pf-t--chart--global--fill--color--900)"; strokeWidth: "--pf-t--chart--global--stroke--width--xs"; }; ... 8 more ...; q3Labels: { ...; }; }; boxWidth: 20; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--...' is not assignable to type '{ style?: { max?: VictoryStyleObject; maxLabels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; median?: VictoryStyleObject; ... 6 more ...; q3Labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; boxWidth?: number; } & VictoryCommonThemeProps'.
  Type '{ style: { max: { padding: 8; stroke: "var(--pf-v6-chart-boxplot--max--stroke--Color, --pf-t--chart--global--fill--color--900)"; strokeWidth: "--pf-t--chart--global--stroke--width--xs"; }; ... 8 more ...; q3Labels: { ...; }; }; boxWidth: 20; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(260,3): error TS2322: Type '{ candleColors: { positive: "var(--pf-v6-chart-candelstick--candle--positive--Color, --pf-t--chart--global--fill--color--white)"; negative: "var(--pf-v6-chart-candelstick--candle--negative--Color, --pf-t--chart--global--fill--color--900)"; }; style: { ...; }; padding: "--pf-t--chart--global--layout--padding"; height...' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; candleColors?: { ...; }; } & VictoryCommonThemeProps'.
  Type '{ candleColors: { positive: "var(--pf-v6-chart-candelstick--candle--positive--Color, --pf-t--chart--global--fill--color--white)"; negative: "var(--pf-v6-chart-candelstick--candle--negative--Color, --pf-t--chart--global--fill--color--900)"; }; style: { ...; }; padding: "--pf-t--chart--global--layout--padding"; height...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(274,3): error TS2322: Type '{ padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--layout--height"; width: "--pf-t--chart--global--layout--width"; }' is not assignable to type 'VictoryCommonThemeProps'.
  Types of property 'height' are incompatible.
    Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(279,5): error TS2322: Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(290,3): error TS2322: Type '{ padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--layout--height"; width: "--pf-t--chart--global--layout--width"; }' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; } & VictoryCommonThemeProps'.
  Type '{ padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--layout--height"; width: "--pf-t--chart--global--layout--width"; }' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(309,3): error TS2322: Type '{ style: { data: { fill: "var(--pf-v6-chart-line--data--Fill, transparent)"; opacity: 1; stroke: "var(--pf-v6-chart-line--data--stroke--Color, --pf-t--chart--global--fill--color--900)"; strokeWidth: "--pf-t--chart--global--stroke--width--sm"; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding";...' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; } & VictoryCommonThemeProps & VictoryDatableProps'.
  Type '{ style: { data: { fill: "var(--pf-v6-chart-line--data--Fill, transparent)"; opacity: 1; stroke: "var(--pf-v6-chart-line--data--stroke--Color, --pf-t--chart--global--fill--color--900)"; strokeWidth: "--pf-t--chart--global--stroke--width--sm"; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding";...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(337,3): error TS2322: Type '{ style: { data: { fill: "var(--pf-v6-chart-scatter--data--Fill, --pf-t--chart--global--fill--color--900)"; opacity: 1; stroke: "var(--pf-v6-chart-scatter--data--stroke--Color, transparent)"; strokeWidth: 0; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--la...' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; }; } & VictoryCommonThemeProps & VictoryDatableProps'.
  Type '{ style: { data: { fill: "var(--pf-v6-chart-scatter--data--Fill, --pf-t--chart--global--fill--color--900)"; opacity: 1; stroke: "var(--pf-v6-chart-scatter--data--stroke--Color, transparent)"; strokeWidth: 0; }; labels: { ...; }; }; padding: "--pf-t--chart--global--layout--padding"; height: "--pf-t--chart--global--la...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts(374,3): error TS2322: Type '{ style: { data: { fill: "var(--pf-v6-chart-voronoi--data--Fill, transparent)"; stroke: "var(--pf-v6-chart-voronoi--data--stroke--Color, transparent)"; strokeWidth: 0; }; labels: { fill: "var(--pf-v6-chart-voronoi--labels--Fill, --pf-t--chart--global--fill--color--200)"; ... 6 more ...; stroke: "var(--pf-v6-chart-gl...' is not assignable to type '{ style?: { data?: VictoryStyleObject; labels?: VictoryLabelStyleObject | VictoryLabelStyleObject[]; flyout?: VictoryStyleObject; }; } & VictoryCommonThemeProps & VictoryDatableProps'.
  Type '{ style: { data: { fill: "var(--pf-v6-chart-voronoi--data--Fill, transparent)"; stroke: "var(--pf-v6-chart-voronoi--data--stroke--Color, transparent)"; strokeWidth: 0; }; labels: { fill: "var(--pf-v6-chart-voronoi--labels--Fill, --pf-t--chart--global--fill--color--200)"; ... 6 more ...; stroke: "var(--pf-v6-chart-gl...' is not assignable to type 'VictoryCommonThemeProps'.
    Types of property 'height' are incompatible.
      Type 'string' is not assignable to type 'number'.
Error: packages/react-charts/src/victory/components/ChartUtils/chart-label.ts(105,34): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
Error: packages/react-charts/src/victory/components/ChartUtils/chart-legend.ts(149,8): error TS2365: Operator '>' cannot be applied to types 'string | number' and 'number'.
Error: packages/react-charts/src/victory/components/ChartBullet/ChartBulletTitle.tsx(207,41): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

@logonoff logonoff force-pushed the 11803-dark-theme-tokens branch from 4d56fec to 733ca08 Compare October 30, 2025 16:58
@patternfly-build
Copy link
Collaborator

patternfly-build commented Oct 30, 2025

@logonoff logonoff force-pushed the 11803-dark-theme-tokens branch from 733ca08 to 9cb59a1 Compare October 30, 2025 17:13
@rebeccaalpert rebeccaalpert requested review from a team, dlabaj and nicolethoen and removed request for a team October 31, 2025 18:37
Copy link
Contributor

@nicolethoen nicolethoen left a comment

Choose a reason for hiding this comment

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

Thank you for this PR! I looked a bit closer into the implementation and token generation and tested it locally.

When i ran it locally to generate the tokens, i'm only seeing 476 tokens have dark values. I think it should be many more than that.

It's currently only extracting dark values for tokens explicitly overridden in dark theme CSS blocks, but does not resolve dark values through the variable dependency chain.

Example:

The semantic token has a dark value:

// t_global_text_color_regular.js
{
  "name": "--pf-t--global--text--color--regular",
  "value": "#151515",      // Light theme: dark text
  "darkValue": "#ffffff"   // Dark theme: white text
}

But the component token that references it does not:
// c_content.js
"c_content_Color": {
  "name": "--pf-v6-c-content--Color",
  "value": "#151515",      // Light theme: resolved correctly
  "values": [
    "--pf-t--global--text--color--regular",  // References token with darkValue
    "--pf-t--global--text--color--100",
    "--pf-t--color--gray--95",
    "#151515"
  ]
  // Missing: "darkValue": "#ffffff"
}

The implementation needs to recursively resolve dark values through the dependency chain. When getVarsMap() encounters a reference to another token, it should check if that token has a dark override and propagate that through the resolution. This would make the feature significantly more useful for developers trying to programmatically access theme values.

Closes patternfly#11803

Enhanced the @patternfly/react-tokens package to include dark theme
values in semantic token definitions, making it easier for developers
to access both light and dark theme values programmatically.

Changes:
- Added getDarkThemeDeclarations() to extract dark theme CSS rules
- Added getDarkLocalVarsMap() to build dark theme variable mappings
- Updated token generation to include darkValue and darkValues properties
- Enhanced variable resolution to support dark theme context
- Updated legacy token support to include dark values

Result:
- 1,998 tokens now include dark theme values
- Tokens with dark overrides expose darkValue property
- Backward compatible with existing code
- Enables programmatic theme switching and consistency

Implements: patternfly#11803

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@logonoff logonoff force-pushed the 11803-dark-theme-tokens branch from 9cb59a1 to 86b8e06 Compare January 26, 2026 17:45
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Walkthrough

The changes extend the token generation pipeline to support dark theme values. The scripts now extract dark theme variable declarations separately, resolve dark-specific values through variable resolution chains with isDark flags, and include darkValue properties in generated token objects when dark values differ from light values.

Changes

Cohort / File(s) Summary
Dark Theme Token Generation
packages/react-tokens/scripts/generateTokens.mjs
Introduces getDarkThemeDeclarations and getDarkLocalVarsMap for dark theme extraction. Extends getComputedCSSVarValue, getVarsMap, and getFromLocalVarsMap with isDark flags to resolve values against merged light+dark variable maps. Adds per-file dark token collection and conditional darkValue attachment to token objects when dark values differ from light values.
Legacy Token Dark Value Export
packages/react-tokens/scripts/writeTokens.mjs
Destructures darkValue from token entries and conditionally attaches it to legacy token exports (oldToken.darkValue).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements the core objective from #11803: extending react-tokens to include dark theme values in semantic token definitions with programmatic access. Address TypeScript build errors in packages/react-charts where string token values are assigned to numeric properties; ensure type compatibility across dependent packages.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding dark theme token values to the react-tokens package.
Out of Scope Changes check ✅ Passed All changes are scoped to react-tokens package for dark theme token handling; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/react-tokens/scripts/generateTokens.mjs (1)

241-277: Dark local var lookup can drop light selectors due to shallow merge.

When isDark is true, { ...localVarsMap, ...darkLocalVarsMap } overwrites entire per-token selector maps if a dark entry exists. That can discard light selectors that should remain as fallbacks when only some selectors are overridden in dark mode, potentially breaking dark value resolution.

🐛 Suggested fix (merge per token key, not top-level)
 const getFromLocalVarsMap = (match, selector, isDark = false) => {
-  const varsMap = isDark ? { ...localVarsMap, ...darkLocalVarsMap } : localVarsMap;
-  if (varsMap[match]) {
+  const baseMap = localVarsMap[match];
+  const darkMap = darkLocalVarsMap[match];
+  const matchMap = isDark ? { ...(baseMap || {}), ...(darkMap || {}) } : baseMap;
+  if (matchMap) {
     // have exact selectors match
-    if (varsMap[match][selector]) {
-      return varsMap[match][selector];
-    } else if (Object.keys(varsMap[match]).length === 1) {
+    if (matchMap[selector]) {
+      return matchMap[selector];
+    } else if (Object.keys(matchMap).length === 1) {
       // only one match, return its value
-      return Object.values(varsMap[match])[0];
+      return Object.values(matchMap)[0];
     } else {
       // find the nearest parent selector and return its value
       let bestMatch = '';
       let bestValue = '';
-      for (const key in varsMap[match]) {
-        if (varsMap[match].hasOwnProperty(key)) {
+      for (const key in matchMap) {
+        if (matchMap.hasOwnProperty(key)) {
           // remove trailing * from key to compare
           let sanitizedKey = key.replace(/\*$/, '').trim();
           sanitizedKey = sanitizedKey.replace(/>$/, '').trim();
           sanitizedKey = sanitizedKey.replace(/\[.*\]$/, '').trim();
           // is key a parent of selector?
           if (selector.indexOf(sanitizedKey) > -1) {
             if (sanitizedKey.length > bestMatch.length) {
               // longest matching key is the winner
               bestMatch = key;
-              bestValue = varsMap[match][key];
+              bestValue = matchMap[key];
             }
           }
         }
       }
🤖 Fix all issues with AI agents
In `@packages/react-tokens/scripts/generateTokens.mjs`:
- Around line 35-45: getDarkThemeDeclarations currently only matches selectors
containing ":where(.pf-${version}-theme-dark)" which misses selectors like
".pf-${version}-theme-dark" or ".pf-${version}-t-dark" and therefore mismatches
getLightThemeDeclarations; create a shared predicate function (e.g.,
isDarkSelector) that checks a selector for any of the dark variants (with or
without :where(), and both "-theme-dark" and "-t-dark") and replace the inline
selector test in getDarkThemeDeclarations and the exclusion test in
getLightThemeDeclarations to use that shared isDarkSelector so all dark theme
declarations are consistently detected and filtered.

Comment on lines +35 to +45
const getDarkThemeDeclarations = (cssAst) =>
cssAst.stylesheet.rules
.filter(
(node) =>
node.type === 'rule' &&
node.selectors &&
node.selectors.some((item) => item.includes(`:where(.pf-${version}-theme-dark)`))
)
.map((node) => node.declarations.filter((decl) => decl.type === 'declaration'))
.reduce((acc, val) => acc.concat(val), []); // flatten

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd packages/react-tokens/scripts && wc -l generateTokens.mjs

Repository: patternfly/patternfly-react

Length of output: 93


🏁 Script executed:

cat -n packages/react-tokens/scripts/generateTokens.mjs | sed -n '1,60p'

Repository: patternfly/patternfly-react

Length of output: 2538


🏁 Script executed:

cat -n packages/react-tokens/scripts/generateTokens.mjs | sed -n '140,160p'

Repository: patternfly/patternfly-react

Length of output: 1037


Dark selector predicate is too narrow and creates a mismatch with light theme filtering.

getDarkThemeDeclarations only matches selectors containing :where(.pf-v6-theme-dark), but getLightThemeDeclarations excludes selectors containing both .pf-v6-theme-dark and .pf-v6-t-dark (without :where()). This creates a gap: if PatternFly CSS uses .pf-v6-theme-dark without :where(), those declarations will be excluded from light but not captured by dark, resulting in missing dark theme tokens.

Extract a shared isDarkSelector predicate and use it consistently for both light and dark filtering to ensure all dark theme declarations are captured.

🔧 Suggested fix
+const isDarkSelector = (selectors = []) =>
+  selectors.some(
+    (item) =>
+      item.includes(`.pf-${version}-theme-dark`) || item.includes(`.pf-${version}-t-dark`)
+  );
+
 const getLightThemeDeclarations = (cssAst) =>
   cssAst.stylesheet.rules
     .filter(
-      (node) =>
-        node.type === 'rule' &&
-        !node.selectors.includes(`.pf-${version}-t-dark`) &&
-        (!node.selectors || !node.selectors.some((item) => item.includes(`.pf-${version}-theme-dark`)))
+      (node) => node.type === 'rule' && !isDarkSelector(node.selectors || [])
     )
     .map((node) => node.declarations.filter((decl) => decl.type === 'declaration'))
     .reduce((acc, val) => acc.concat(val), []); // flatten
 
 const getDarkThemeDeclarations = (cssAst) =>
   cssAst.stylesheet.rules
-    .filter(
-      (node) =>
-        node.type === 'rule' &&
-        node.selectors &&
-        node.selectors.some((item) => item.includes(`:where(.pf-${version}-theme-dark)`))
-    )
+    .filter((node) => node.type === 'rule' && isDarkSelector(node.selectors || []))
     .map((node) => node.declarations.filter((decl) => decl.type === 'declaration'))
     .reduce((acc, val) => acc.concat(val), []); // flatten
🤖 Prompt for AI Agents
In `@packages/react-tokens/scripts/generateTokens.mjs` around lines 35 - 45,
getDarkThemeDeclarations currently only matches selectors containing
":where(.pf-${version}-theme-dark)" which misses selectors like
".pf-${version}-theme-dark" or ".pf-${version}-t-dark" and therefore mismatches
getLightThemeDeclarations; create a shared predicate function (e.g.,
isDarkSelector) that checks a selector for any of the dark variants (with or
without :where(), and both "-theme-dark" and "-t-dark") and replace the inline
selector test in getDarkThemeDeclarations and the exclusion test in
getLightThemeDeclarations to use that shared isDarkSelector so all dark theme
declarations are consistently detected and filtered.

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.

react-tokens - include dark theme value in semantic token definitions

4 participants