# How to Create Custom Astryx Themes Beyond the Built-In Options

> Learn the recommended pattern for creating custom Astryx themes. Export a theme object and pass it to the XDSTheme provider without forking the library.

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

---

**The recommended pattern for Astryx custom themes involves exporting a theme object that matches the built-in theme shape and passing it to the `XDSTheme` provider, without forking the library.**

Astryx themes are CSS custom-property collections consumed by the design system's provider layer. Whether you need a corporate rebrand, dark mode variant, or specialized accessibility palette, you can create fully functional themes as separate packages or local modules. This guide demonstrates the official pattern used by the `facebook/astryx` source code for extending the theme system beyond neutral, stone, and other built-in options.

## Core Concept: Theme as a Structured Object

Astryx treats themes as structured configuration objects rather than style overrides. The `XDSTheme` provider in `packages/core/src/theme/` reads CSS custom properties at runtime, which means your theme only needs two components: a TypeScript/JavaScript object defining tokens, and a CSS file declaring the corresponding custom properties.

The `createTheme` helper from `@astryxdesign/core/theme` ensures your object conforms to the expected interface defined in [`packages/core/src/utils/themeProps.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/utils/themeProps.ts), which specifies required keys for colors, typography, spacing, and other design tokens.

## Step-by-Step: Creating a Custom Astryx Theme

### 1. Scaffold Your Theme Package

Create a new package or local module that exports a theme object. The structure mirrors built-in themes like `packages/themes/stone/`:

```

my-custom-theme/
├── src/
│   ├── theme.ts          # Exports the theme object

│   └── built/
│       └── theme.css     # Generated CSS custom properties

├── package.json
└── README.md

```

Reference [`packages/themes/stone/README.md`](https://github.com/facebook/astryx/blob/main/packages/themes/stone/README.md) in the Astryx repository for the standard layout, including "Install", "Usage", and "Import paths" sections.

### 2. Define Tokens with `createTheme`

Use the exposed helper to build your theme object with the same keys as built-in themes:

```tsx
// my-custom-theme/src/theme.ts
import {createTheme} from '@astryxdesign/core/theme';
import {stylex} from '@stylexjs/stylex';

export const myTheme = createTheme({
  // Colors – must match built-in theme keys
  color: {
    brand: '#0062FF',
    background: '#FAFAFA',
    textPrimary: '#111111',
    textSecondary: '#6B7280',
    border: '#E5E7EB',
    success: '#10B981',
    warning: '#F59E0B',
    error: '#EF4444',
  },

  // Typography
  fontFamily: {
    body: 'Inter, sans-serif',
    heading: 'Roboto, sans-serif',
    code: 'Source Code Pro, monospace',
  },

  // Spacing with StyleX variable definitions
  space: stylex.defineVars({ 
    sm: '4px', 
    md: '8px', 
    lg: '16px',
    xl: '24px',
    xxl: '32px',
  }),

  // Border radius
  radius: stylex.defineVars({ 
    small: '2px', 
    medium: '4px',
    large: '8px',
  }),
});

```

The `createTheme` helper validates against the interface in [`packages/core/src/utils/themeProps.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/utils/themeProps.ts), which defines the shape all themes must follow. Missing required keys will surface as TypeScript errors.

### 3. Generate or Write the CSS File

The CSS file declares custom properties that match your theme tokens. You have two options:

- **Build-generated**: Run `@astryxdesign/build` (StyleX-based) to auto-generate CSS from your theme object
- **Hand-written**: Create the file manually following the variable naming convention

```css
/* my-custom-theme/src/built/theme.css (auto-generated or hand-written) */
:root {
  --color-brand: #0062FF;
  --color-background: #FAFAFA;
  --color-text-primary: #111111;
  --color-text-secondary: #6B7280;
  --color-border: #E5E7EB;
  --color-success: #10B981;
  --color-warning: #F59E0B;
  --color-error: #EF4444;
  
  --font-family-body: 'Inter, sans-serif';
  --font-family-heading: 'Roboto, sans-serif';
  --font-family-code: 'Source Code Pro, monospace';
  
  --space-sm: 4px;
  --space-md: 8px;
  --space-lg: 16px;
  --space-xl: 24px;
  --space-xxl: 32px;
  
  --radius-small: 2px;
  --radius-medium: 4px;
  --radius-large: 8px;
}

```

Import paths follow the pattern documented in `packages/cli/assets/docs/theme.doc.mjs`: base themes use `@astryxdesign/theme-<name>` while built/compiled assets use `@astryxdesign/theme-<name>/built`.

### 4. Apply with `XDSTheme` Provider

Wrap your application and pass the custom theme object. The provider reads CSS custom properties at runtime—no additional wrappers needed:

```tsx
// App.tsx
import {XDSTheme} from '@astryxdesign/core/theme';
import {myTheme} from 'my-custom-theme/src/theme';

// Import base theme (optional) and your custom theme CSS
import '@astryxdesign/theme-neutral/theme.css';    // optional foundation
import 'my-custom-theme/src/built/theme.css';      // your custom properties

function App() {
  return (
    <XDSTheme theme={myTheme}>
      <YourApplication />
    </XDSTheme>
  );
}

```

The `XDSTheme` component in `packages/core/src/theme/` handles runtime property resolution. Your theme object provides the token structure; the CSS file provides the actual computed values.

### 5. (Optional) Register for CLI Discovery

To enable `astryx theme list` to discover your theme, add an entry to the central registry:

```ts
// packages/core/src/theme/themeRegistry.ts
import {myTheme} from 'my-custom-theme/src/theme';

export const themeRegistry = {
  neutral: neutralTheme,
  stone: stoneTheme,
  dark: darkTheme,
  // ...built-in themes
  'my-custom': myTheme,   // ← register for CLI visibility
};

```

The [`themeRegistry.ts`](https://github.com/facebook/astryx/blob/main/themeRegistry.ts) file maps string names to exported theme objects. Registration is only required for CLI integration—your theme functions fully without this step.

## Distribution Options for Custom Themes

The decoupled architecture supports multiple distribution patterns:

| Pattern | Best For | Implementation |
|---------|----------|----------------|
| **NPM package** | Organization-wide sharing | Publish to private or public registry; consumers import as `@yourscope/theme-name` |
| **Git submodule** | Monorepo with shared themes | Add as submodule; import via relative path or workspace protocol |
| **Local folder** | Single application customization | Co-locate in project; import from local path |
| **Upstream contribution** | Themes suitable for core library | Follow `packages/themes/stone/` structure; submit PR to `facebook/astryx` |

All patterns use identical import and consumption code—only the package resolution differs.

## Key Source Files Reference

Understanding these files in the `facebook/astryx` repository ensures your theme remains compatible:

- [`packages/core/src/utils/themeProps.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/utils/themeProps.ts) — Interface definition for theme object shape; validate your tokens against this structure
- [`packages/core/src/theme/themeRegistry.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/themeRegistry.ts) — Central name-to-object mapping for CLI discovery
- `packages/cli/assets/docs/theme.doc.mjs` — Official documentation for import path conventions
- [`packages/themes/stone/README.md`](https://github.com/facebook/astryx/blob/main/packages/themes/stone/README.md) — Template for package structure and consumer-facing documentation

## Common Pitfalls and Solutions

**Missing CSS import**: The theme object alone does not render styles—you must import the CSS file defining custom properties. The `XDSTheme` provider resolves values at runtime from the CSS cascade.

**Mismatched keys**: Token keys must exactly match the interface in [`themeProps.ts`](https://github.com/facebook/astryx/blob/main/themeProps.ts). Use `createTheme` to catch discrepancies at build time.

**StyleX version conflicts**: When using `stylex.defineVars()` for spacing or other tokens, ensure your package's `@stylexjs/stylex` version aligns with `@astryxdesign/core`'s dependency range.

## Summary

- **Custom Astryx themes** are structured objects consumed by `XDSTheme`, not style overrides or forked components
- **Use `createTheme`** from `@astryxdesign/core/theme` to ensure interface compliance with [`packages/core/src/utils/themeProps.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/utils/themeProps.ts)
- **Ship a CSS file** declaring custom properties—auto-generated via `@astryxdesign/build` or hand-written
- **Distribute as npm packages**, git submodules, or local folders without core library modifications
- **Register in [`themeRegistry.ts`](https://github.com/facebook/astryx/blob/main/themeRegistry.ts)** only if CLI discovery via `astryx theme list` is required

## Frequently Asked Questions

### Do I need to fork Astryx to create a custom theme?

No. Themes are intentionally decoupled from core components. You create a standalone package or module that exports a theme object and CSS file, then pass that object to `XDSTheme`. The source code in `facebook/astryx` demonstrates this through the built-in theme packages, which follow the same external package pattern available to custom implementations.

### What happens if my theme object is missing required keys?

TypeScript will surface errors at build time because `createTheme` validates against the interface in [`packages/core/src/utils/themeProps.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/utils/themeProps.ts). Runtime behavior depends on component implementation—some may fall back to defaults, others may render incorrectly. Always use `createTheme` rather than plain objects to ensure complete coverage.

### Can I override only specific tokens from a built-in theme?

Yes. Import a base theme object, use object spread to modify specific tokens, and pass the result to `createTheme`. The CSS file approach also supports layering: import a base theme's CSS, then import your override CSS with redefined custom properties for selective replacement.