Carbon Dark Mode: Theming React Apps with Design Tokens Instead of Hard-Coded Colors
Hard-coded colors make dark mode painful. Learn how to use Carbon's CSS custom properties and theme classes to toggle dark mode in React with a single root class change.
21 Nov 2025, 22:05 UTC

Hard-coding colors in a React application turns dark mode into a find-and-replace nightmare. Every component with background: #ffffff or color: #161616 needs a conditional override, and you end up maintaining two parallel style sheets. IBM's Carbon Design System avoids this by exposing theming as CSS custom properties—design tokens—so a single class change on a root element updates the entire interface.
This article walks through a token-first migration for dark mode using Carbon, the practical steps to verify it, and the limitations you should plan for before shipping.
How Carbon Theming Works
Carbon components do not reference literal color values. Instead, they consume CSS custom properties such as --cds-background, --cds-text-primary, and --cds-border-subtle. These tokens are defined by theme classes that you apply to a root element. Because the browser resolves custom properties at runtime, changing the class on a parent container cascades new values to every descendant without re-rendering React components or duplicating CSS rules.
Carbon ships with four standard themes exposed as CSS classes:
cds--white— light themecds--g10— light-cool variantcds--g90— dark themecds--g100— high-contrast dark theme
You can apply these to the html element, a wrapping div, or a scoped container. In React, the @carbon/react package (or the older carbon-components-react, depending on your version) may also provide a Theme context component for programmatic switching, but the underlying mechanism remains CSS classes and custom properties.
Why Tokens Beat Conditional Styling
The alternative—writing isDark ? '#161616' : '#ffffff' in every component—scatters theme logic across your codebase. Carbon's token model centralizes the decision. If a designer later adjusts the dark-mode background from #262626 to #393939, you update one token definition rather than hunting through component files.
This approach also works outside React. Vanilla HTML, Vue, and Angular applications can import Carbon's stylesheet, apply a theme class, and consume the same tokens. The portability comes from the CSS layer, not the React bindings.
A Practical Migration Path
Suppose you have a React dashboard with legacy styles that hard-code colors. The migration follows three phases: audit, replace, verify.
1. Audit Literal Values
Search your codebase for hex codes, rgb(), and hsl() values in CSS, styled-components, and inline style objects. Prioritize colors that map to Carbon's semantic tokens. For example, a card background of #f4f4f4 likely corresponds to var(--cds-layer) or var(--cds-background), while primary text of #161616 maps to var(--cds-text-primary).
2. Replace with Tokens
Update your styles to reference the custom properties:
.dashboard-card {
background-color: var(--cds-layer);
color: var(--cds-text-primary);
border: 1px solid var(--cds-border-subtle);
}
If you use CSS-in-JS, the same variables work inside template literals or style objects. Avoid overriding Carbon's own component styles directly; instead, wrap components in a themed container and let the tokens flow down.
3. Verify Contrast and Computed Values
Before shipping, confirm that both themes render correctly:
- Run
npm ls carbon-components-reactornpm ls @carbon/reactin your project root. The expected check is a resolved version number; if the command returnsemptyorUNMET DEPENDENCY, your package layout differs from the standard Carbon installation and you should inspectnode_modulesfor the actual theme exports before writing code. - Open your application in a browser, open dev tools, and manually toggle the theme class on the root element. Inspect a component and check the computed styles panel to verify that
background-colorandcolorresolve to the expected token values. - Use the browser's accessibility inspector or a contrast checker to verify that text meets WCAG AA contrast ratios under both
cds--whiteandcds--g100. Carbon's default tokens are designed for accessibility, but custom overrides can break contrast. - If your project has visual regression tests, run them against both themes to catch unintended style leaks.
Trade-offs and Limitations
Token-based theming is not free of edge cases.
Server-side rendering flash. If your React app uses SSR or static generation, the server may emit the default cds--white class before the client applies the user's saved preference. The result is a flash of light theme on a dark-mode device. Mitigate this with a small blocking script in the document head that reads localStorage or a cookie and sets the class before first paint, or default to the theme that matches the user's prefers-color-scheme media query.
Token overrides are design-system changes. Redefining --cds-background in your application CSS feels like a local tweak, but it affects every Carbon component in scope. If you change a token to a custom color, you assume responsibility for contrast across all states—hover, focus, disabled, and error. Treat these overrides as design-system modifications and re-run your contrast checks.
Version drift. Carbon's React packaging has evolved. The older carbon-components-react package and the newer @carbon/react package may expose different Theme components, context APIs, and token names. Do not assume an import path is stable; verify against the version in your package.json and inspect the package's exported files or documentation for your specific release.
Actionable Checklist
- Confirm your Carbon package name and version with
npm lsin the project root. - Replace hard-coded colors with semantic Carbon tokens in your own styles.
- Apply theme classes at the root or a scoped wrapper, not per-component.
- Toggle
cds--whiteandcds--g100in dev tools and inspect computed values. - Verify contrast ratios in both themes before merging.
- Add a blocking script or CSS default to prevent SSR theme flash.
Carbon's token model shifts dark mode from a per-component chore to a single class change. The upfront cost is auditing your literal colors; the payoff is a theme layer that designers and developers can adjust without touching component logic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.