Skip to content

Add support for derived color modes#1333

Open
danielguillan wants to merge 22 commits into
mainfrom
danielguillan/color-scheme-attribute
Open

Add support for derived color modes#1333
danielguillan wants to merge 22 commits into
mainfrom
danielguillan/color-scheme-attribute

Conversation

@danielguillan
Copy link
Copy Markdown
Collaborator

@danielguillan danielguillan commented Apr 30, 2026

Summary

Towards https://github.com/github/brand-experience/issues/80

ThemeProvider only recognizes light, dark, and auto as color modes. Consumers that need derived modes (e.g. dark_dimmed, light_high_contrast) can't pass them through without breaking dark/light-specific component styling.

Adds a data-color-scheme attribute next to the existing data-color-mode and determines if a given derived theme is "light" or "dark" automatically.

List of notable changes:

  • ThemeProvider maps color modes to "light" or "dark" (adding support for derived themes without explicitly including them).
  • Added data-color-scheme to set light or dark for the derived themes.

What should reviewers focus on?

  • Using auto and/or passing a derived mode like dark_dimmed to ThemeProvider should resolve to the correct scheme styling.
  • Nesting ThemeProviders with different color modes should continue working with no leaks.

Steps to test:

Supporting resources (related issues, external links, etc):

Contributor checklist:

  • All new and existing CI checks pass
  • Tests prove that the feature works and covers both happy and unhappy paths
  • Any drop in coverage, breaking changes or regressions have been documented above
  • UI Changes contain new visual snapshots (generated by adding update snapshots label to the PR)
  • All developer debugging and non-functional logging has been removed
  • Related issues have been referenced in the PR description

Reviewer checklist:

  • Check that pull request and proposed changes adhere to our contribution guidelines and code of conduct
  • Check that tests prove the feature works and covers both happy and unhappy paths
  • Check that there aren't other open Pull Requests for the same update/change

Screenshots:

No visual changes expected — components should look the same as before.

image
screenshot-HiYRhVnZ-001022.mp4

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: 9a8794a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@primer/react-brand Minor
@primer/brand-primitives Minor
@primer/brand-docs Minor
@primer/brand-css Minor
@primer/brand-e2e Minor
@primer/brand-fonts Minor
@primer/brand-config Minor
@primer/brand-storybook Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@danielguillan danielguillan changed the title Danielguillan/color scheme attribute Add support for derivative color modes Apr 30, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🔍 Design token changes found

View CSS variable changes
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],
- [data-color-mode="light"] {
+ [data-color-mode="light"],
- [data-color-mode="dark"] {
+ [data-color-mode="dark"],

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🟢 Unit test coverage changes found

Unit test coverage has been updated through this PR.

Changes: 0 new tests, 0 removed tests, 2 improved, 0 decreased

Component/Hook Statements Functions Branches Change
IDE 91.2% 91.6% 97.4% 82.8% 83.4% +0.7%
ThemeProvider 96.7% 96.9% 88.9% 90.0% 81.8% 84.6% +2.8%

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🟢 No visual differences found

Our visual comparison tests did not find any differences in the UI.

@danielguillan danielguillan changed the title Add support for derivative color modes Add support for derived color modes May 5, 2026
@danielguillan danielguillan marked this pull request as ready for review May 8, 2026 15:50
Copilot AI review requested due to automatic review settings May 8, 2026 15:50
@danielguillan danielguillan requested a review from a team as a code owner May 8, 2026 15:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class support for “derived” color modes (e.g. dark_dimmed, light_high_contrast) by having ThemeProvider expose both the requested data-color-mode and a derived data-color-scheme (light/dark) so existing dark/light-specific styling and token application continue to work.

Changes:

  • Extend ThemeProvider to accept arbitrary color mode strings, compute a base scheme via getColorScheme, and render data-color-scheme.
  • Update component/recipe CSS selectors to treat data-color-scheme the same as data-color-mode for light/dark conditional styles.
  • Update design-token CSS output to apply light/dark token sets via [data-color-scheme="light"|"dark"], plus add Storybook and visual coverage for derived modes.
Show a summary per file
File Description
packages/react/src/Token/Token.module.css Minor diff; still contains dark-only selector for Token styling.
packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts Adds a visual regression test for the derived color modes story.
packages/react/src/ThemeProvider/ThemeProvider.tsx Adds getColorScheme, widens ColorMode, and renders data-color-scheme.
packages/react/src/ThemeProvider/ThemeProvider.test.tsx Adds assertions and unit tests for derived modes and getColorScheme.
packages/react/src/ThemeProvider/ThemeProvider.stories.tsx Adds a “DerivedColorModes” story and control options for derived modes.
packages/react/src/Text/Text.module.css Makes antialiasing conditional on dark scheme as well as dark mode.
packages/react/src/SubNav/SubNav.module.css Makes antialiasing conditional on dark scheme as well as dark mode.
packages/react/src/recipes/SolutionTemplates/SolutionPage/SolutionPage.module.css Extends light/dark hero glow styling to also respond to scheme.
packages/react/src/recipes/SolutionTemplates/CategoryPage/CategoryPage.module.css Extends light/dark hero styling to also respond to scheme.
packages/react/src/recipes/seo/Category/CategoryPage.module.css Updates dark-only hover/selected styling to also respond to scheme.
packages/react/src/recipes/FlexTemplate/FlexSection/FlexSection.tsx Uses getColorScheme for dark/light background image selection.
packages/react/src/recipes/FlexTemplate/FlexSection/components/FlexSectionTestimonials.tsx Uses getColorScheme to select correct light/dark decorative assets.
packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.tsx Uses getColorScheme for light/dark image selection and simplifies logic.
packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.module.css Extends dark hover styling to also respond to scheme.
packages/react/src/recipes/FeaturePreviewLPs/FeaturePreviewLevelTwo/FeaturePreviewLevelTwo.module.css Extends dark trailing-section styling to also respond to scheme.
packages/react/src/recipes/FeaturePreviewLPs/FeaturePreviewLevelOne/FeaturePreviewLevelOne.module.css Extends dark trailing-section and form styling to also respond to scheme.
packages/react/src/MinimalFooter/MinimalFooter.tsx Uses getColorScheme to choose correct logo fill for derived modes.
packages/react/src/MinimalFooter/MinimalFooter.test.tsx Adds unit test to ensure derived dark modes use the dark logo fill.
packages/react/src/MinimalFooter/MinimalFooter.module.css Extends light-only social icon filter styling to also respond to scheme.
packages/react/src/Label/Label.module.css Extends dark antialiasing styling to also respond to scheme.
packages/react/src/InlineLink/InlineLink.module.css Extends dark font-smoothing styling to also respond to scheme.
packages/react/src/Hero/Hero.stories.module.css Extends story-only light/dark vars to also respond to scheme.
packages/react/src/Heading/Heading.module.css Extends dark font-smoothing styling to also respond to scheme.
packages/react/src/Card/CardSkewEffect.tsx Uses getColorScheme so skew behavior is consistent for derived modes.
packages/react/src/Card/Card.stories.shared.module.css Extends dark hover styling to also respond to scheme.
packages/react/src/Accordion/Accordion.module.css Extends dark content font-smoothing styling to also respond to scheme.
packages/design-tokens/src/formats/color-mode-attributes.test.js Updates expected CSS output to include data-color-scheme selectors.
packages/design-tokens/src/formats/color-mode-attributes.js Outputs token selectors for `[data-color-scheme="light"
.changeset/lazy-wolves-win.md Documents the release impact for react-brand and brand-primitives.

Copilot's findings

Comments suppressed due to low confidence (2)

packages/react/src/Token/Token.module.css:32

  • Token still gates its dark-mode font-smoothing on [data-color-mode='dark'] only, so derived dark modes (e.g. dark_dimmed) will not get the intended dark styling even though the provider now sets data-color-scheme="dark". Update this selector to also match [data-color-scheme='dark'] (consistent with the other modules changed in this PR).
[data-color-mode='dark'] .Token {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: auto;
}

packages/react/src/ThemeProvider/ThemeProvider.tsx:62

  • When colorMode is auto, activeMode is initialized to 'auto' on the first render, so data-color-scheme will initially render as "light" (via getColorScheme('auto')) even if the system preference is dark. With the new [data-color-scheme] token selectors, this can cause a visible flash/incorrect tokens (especially for nested providers) until the effect runs. Consider initializing state with the resolved auto mode on the client (guarding window for SSR), e.g. useState(() => (colorMode === AUTO && typeof window !== 'undefined' ? getActiveAutoMode() : colorMode)).
export function ThemeProvider({colorMode = defaultMode, children, ...rest}: PropsWithChildren<ThemeProviderProps>) {
  const [activeMode, setActiveMode] = useState(colorMode)
  const availableColorModes = useMemo(() => Object.values(ColorModesEnum), [])

  useEffect(() => {
    if (colorMode === ColorModesEnum.AUTO) {
      setActiveMode(getActiveAutoMode())
    } else if (activeMode !== colorMode) {
      setActiveMode(colorMode)
    }

    return handleSystemPreferenceChange(setActiveMode)
  }, [colorMode, activeMode, setActiveMode])

  return (
    <ThemeContext.Provider value={{colorMode: activeMode, availableColorModes}}>
      <div data-color-mode={activeMode} data-color-scheme={getColorScheme(activeMode)} {...rest}>
        {children}
  • Files reviewed: 29/30 changed files
  • Comments generated: 2

Comment thread packages/react/src/ThemeProvider/ThemeProvider.tsx
Comment thread packages/design-tokens/src/formats/color-mode-attributes.js Outdated
@github-actions
Copy link
Copy Markdown
Contributor

🟢 Bundle size report

CheckMainBranchChange
UMD — full bundle (JS)96.44 kB96.43 kB✅ -9 B (-0.0%)
UMD — full bundle (CSS)63.11 kB63.23 kB⬆️ +127 B (+0.2%)
ESM — full bundle (JS + CSS)1.45 MB1.46 MB⬆️ +1.75 kB (+0.1%)
ESM — tree-shaken simple (Button)66.23 kB66.25 kB⬆️ +17 B (+0.0%)
ESM — tree-shaken complex (ActionMenu)75.05 kB75.11 kB⬆️ +64 B (+0.1%)

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.

2 participants