# How to Implement Dark Mode Support in Astryx Theming

> Learn how to implement dark mode in Astryx theming using XDSTheme provider and CSS custom properties. Apply dark or light themes easily.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-07-02

---

**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`](https://github.com/facebook/astryx/blob/main/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`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/defineTheme.ts). Start by importing your base light tokens and any dark-specific overrides.

```typescript
// 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)')`.

```tsx
// 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`](https://github.com/facebook/astryx/blob/main/packages/themes/gothic/README.md), are designed to be dark-only. For these implementations, pass `mode="dark"` explicitly to prevent automatic light-mode detection.

```tsx
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

- **`XDSTheme`** in [`packages/core/src/theme/XDSTheme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/XDSTheme.tsx) handles dark mode by managing CSS custom properties and root element classes.
- Use **`defineTheme`** from [`packages/core/src/theme/defineTheme.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/defineTheme.ts) to structure your token files with optional `@media (prefers-color-scheme: dark)` blocks.
- The **`mode`** prop 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.md`](https://github.com/facebook/astryx/blob/main/packages/themes/neutral/README.md) for examples of bidirectional light and dark themes.
- Theme scaffolding is available through `scripts/generate-cli-themes.mjs` for 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`](https://github.com/facebook/astryx/blob/main/tokens.stylex.ts) and [`tokens-dark.stylex.ts`](https://github.com/facebook/astryx/blob/main/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`](https://github.com/facebook/astryx/blob/main/packages/themes/gothic/README.md), this configuration prevents the provider from checking system preferences and ensures the dark token set is always active.