How to Implement Dark Mode Support in Astryx Theming
Astryx implements dark mode through the XDSTheme provider, which reads the mode prop to inject CSS custom properties and applies .xds-theme-dark or .xds-theme-light classes to the root element, defaulting to the user's prefers-color-scheme setting when no mode is specified.
The theming system in the facebook/astryx repository uses a token-driven architecture where dark mode is handled natively by the core theme provider. By defining color tokens and dark mode overrides, you can create themes that automatically adapt to system preferences or maintain a forced color scheme.
How the XDSTheme Provider Handles Dark Mode
According to the source code in packages/core/src/theme/XDSTheme.tsx, the XDSTheme component manages dark mode through three specific actions. First, it reads the optional mode prop, which accepts "light", "dark", or undefined for auto-detection. When rendering, it appends either .xds-theme-dark or .xds-theme-light to the root element's class list. Finally, it injects a <style> tag containing the generated CSS custom-property values corresponding to the selected mode.
This design ensures that the same theme object can service both light and dark appearances without requiring separate component trees.
Step-by-Step Implementation
Define Base Theme Tokens
Create a theme definition file using the defineTheme helper from packages/core/src/theme/defineTheme.ts. Start by importing your base light tokens and any dark-specific overrides.
// src/theme/myTheme.ts
import { defineTheme } from '@astryxdesign/core/theme';
import { lightTokens } from './tokens.stylex';
import { darkTokens } from './tokens-dark.stylex';
export const myTheme = defineTheme({
// light-mode tokens
...lightTokens,
// dark-mode overrides using media query
'@media (prefers-color-scheme: dark)': {
...darkTokens,
},
});
Add Dark Mode Overrides
You have two methods for supplying dark values. Method A embeds the overrides directly in the theme definition using the @media (prefers-color-scheme: dark) block, as shown above. Method B supplies a separate dark token set that the provider switches to when mode="dark" is passed explicitly. The defineTheme function normalizes both approaches into the CSS custom properties consumed by XDSTheme.
Configure the XDSTheme Provider
Wrap your application root with the provider, passing the theme object and optionally controlling the mode. When the mode prop is omitted, the component automatically detects the system preference via window.matchMedia('(prefers-color-scheme: dark)').
// src/App.tsx
import { XDSTheme } from '@astryxdesign/core/theme';
import { myTheme } from './theme/myTheme';
function App() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return (
<XDSTheme theme={myTheme} mode={prefersDark ? 'dark' : 'light'}>
{/* application components */}
</XDSTheme>
);
}
Forcing Dark Mode for Single-Mode Themes
Some themes, such as the gothic theme referenced in packages/themes/gothic/README.md, are designed to be dark-only. For these implementations, pass mode="dark" explicitly to prevent automatic light-mode detection.
import { XDSTheme } from '@astryxdesign/core/theme';
import { gothicTheme } from '@astryxdesign/theme-gothic';
function App() {
return (
<XDSTheme theme={gothicTheme} mode="dark">
{/* UI renders in dark mode regardless of system setting */}
</XDSTheme>
);
}
The XDSTheme provider will apply the .xds-theme-dark class and inject the appropriate CSS custom properties from the token set, ensuring consistent styling across all components.
Summary
XDSThemeinpackages/core/src/theme/XDSTheme.tsxhandles dark mode by managing CSS custom properties and root element classes.- Use
defineThemefrompackages/core/src/theme/defineTheme.tsto structure your token files with optional@media (prefers-color-scheme: dark)blocks. - The
modeprop accepts"dark","light", or auto-detects when omitted, allowing flexible control over the color scheme. - For always-dark themes like gothic, explicitly set
mode="dark"to bypass system preference detection. - Reference
packages/themes/neutral/README.mdfor examples of bidirectional light and dark themes. - Theme scaffolding is available through
scripts/generate-cli-themes.mjsfor generating boilerplate token files.
Frequently Asked Questions
How does Astryx detect system dark mode preferences?
When the mode prop is omitted from XDSTheme, the component queries the browser's prefers-color-scheme media feature. It evaluates window.matchMedia('(prefers-color-scheme: dark)').matches and automatically applies the .xds-theme-dark class when the system reports a dark preference, otherwise defaulting to .xds-theme-light.
Can I use separate files for dark mode tokens?
Yes. While you can embed dark values directly in the theme definition using @media (prefers-color-scheme: dark) blocks, the defineTheme helper also accepts separate dark token imports. The provider switches between these token sets when you explicitly pass mode="dark" or mode="light", making it easy to maintain distinct files for tokens.stylex.ts and tokens-dark.stylex.ts.
What is the difference between auto mode and explicit dark mode?
Auto mode occurs when you omit the mode prop, allowing XDSTheme to react to system-level changes in real-time. Explicit dark mode requires passing mode="dark", which forces the .xds-theme-dark class and corresponding CSS custom properties regardless of the user's operating system setting. Explicit mode is essential for single-mode themes like the gothic theme that must remain dark in all contexts.
How do I create a theme that is always dark?
Import your dark-only theme tokens and pass mode="dark" to the XDSTheme provider. As demonstrated in the gothic theme example at packages/themes/gothic/README.md, this configuration prevents the provider from checking system preferences and ensures the dark token set is always active.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →