# How the Astryx Theme Provider Handles Runtime vs Pre‑built CSS

> Discover how the Astryx Theme Provider manages runtime and pre-built CSS. Learn asset injection for dynamic themes versus attribute synchronization for CLI-compiled ones.

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

---

**The Astryx Theme component injects CSS style tags dynamically at runtime for developer‑defined themes, but skips injection entirely and only synchronizes HTML attributes when using pre‑built themes compiled via the CLI.**

The `Theme` provider in the `facebook/astryx` repository serves as the entry point for applying design system tokens to React applications. It operates in two distinct modes—runtime injection for dynamic themes and static optimization for pre‑built themes—controlled by the internal `__built` flag generated by the `defineTheme` utility.

## Architecture of the Theme Provider

The provider distinguishes between runtime and pre‑built CSS through a boolean flag set during theme definition. When you call `defineTheme()`, the resulting object includes metadata that tells the provider whether to inject styles dynamically or rely on existing static CSS files.

In [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx), the component uses `useThemeStyleInjection` (lines 97‑176) to manage CSS insertion. This hook checks the `theme.__built` property (line 113) before performing any DOM operations. If the flag is present, the provider skips injection entirely and proceeds only to attribute synchronization.

## Runtime CSS Injection Mode

When using a theme created directly with `defineTheme()` without the CLI build step, the provider enters runtime injection mode. This approach generates CSS during component execution and inserts it into the document head.

**Style tag generation** occurs inside a `useInsertionEffect` hook to ensure styles exist before painting. The provider creates two distinct `<style>` elements:
- One targeting `@layer reset` for prose defaults
- One targeting `@layer astryx‑theme` for component overrides

Each style tag receives a unique identifier based on the theme name and mode. The injection logic resides in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx) lines 122‑133, where the system also emits a development warning urging developers to use pre‑built themes for production performance.

```tsx
// packages/core/src/theme/Theme.tsx (conceptual)
useInsertionEffect(() => {
  if (theme.__built) return;
  
  const styleId = `astryx-theme-${theme.name}`;
  // Inject @layer reset and @layer astryx-theme styles
  document.head.appendChild(styleTag);
}, [theme]);

```

When the component unmounts, the provider automatically removes these tags and deregisters the theme from the internal `injectedThemes` tracking set.

## Pre‑built CSS Mode

Pre‑built themes bypass runtime injection entirely. When you run `npx astryx theme build <file>`, the CLI generates a CSS file and a JavaScript module that sets `theme.__built = true`.

In this mode, `useThemeStyleInjection` detects the built flag (line 113 in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx)) and returns immediately without creating DOM elements. The provider assumes the host application has already loaded the compiled CSS—typically imported from `node_modules/@astryxdesign/theme-{name}/theme.css`.

**Root synchronization** still occurs regardless of the mode. The first `Theme` component in the tree (identified via `ThemeNestingContext`) executes `useRootThemeSync` (lines 182‑214) to write `data-theme` and `data-astryx-theme` attributes to the `<html>` element. This enables browser‑native color scheme handling and ensures scoped CSS reaches portals.

## Root Theme Synchronization

The synchronization mechanism distinguishes between root and nested themes. Only the root provider updates the document element:

- **`data-theme`**: Set to `light`, `dark`, or removed for `system` mode
- **`data-astryx-theme`**: Set to the theme's name attribute

This implementation in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx) (lines 182‑208) ensures that scrollbars, form controls, and date pickers respect the current color scheme. Nested themes skip this sync to prevent conflicting attribute writes while still providing theme context to their subtrees.

## Practical Implementation Examples

### Defining a Runtime Theme

Create a theme using `defineTheme` from [`packages/core/src/theme/defineTheme.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/defineTheme.ts):

```tsx
import {defineTheme} from '@astryxdesign/core/theme';

export const ocean = defineTheme({
  name: 'ocean',
  tokens: {
    '--color-accent': ['#0077B6', '#48CAE4'],
    '--color-bg': ['#E0FBFC', '#1D3557'],
  },
  components: {
    button: {base: {borderRadius: '4px'}},
  },
  icons: oceanIcons,
});

```

### Using Runtime Injection

Apply the theme without pre‑building:

```tsx
import {Theme} from '@astryxdesign/core/theme';
import {ocean} from './theme';

function App() {
  return (
    <Theme theme={ocean} mode="system">
      <YourApp />
    </Theme>
  );
}

```

The provider injects the generated CSS on the first render.

### Building a Pre‑built Theme

Compile the theme via CLI:

```bash
npx astryx theme build src/theme/ocean.ts

```

This creates:
- `node_modules/@astryxdesign/theme-ocean/built/index.js` (with `__built: true`)
- `node_modules/@astryxdesign/theme-ocean/theme.css`

### Using Pre‑built CSS

Import the CSS and use the built theme:

```tsx
import {Theme} from '@astryxdesign/core/theme';
import {oceanTheme} from '@astryxdesign/theme-ocean/built';
import '@astryxdesign/theme-ocean/theme.css';

function App() {
  return (
    <Theme theme={oceanTheme} mode="dark">
      <YourApp />
    </Theme>
  );
}

```

No runtime injection occurs; the provider only synchronizes HTML attributes.

### Nesting Themes

Override themes for specific subtrees:

```tsx
<Theme theme={lightTheme}>
  <Header />
  <Theme theme={darkTheme} mode="dark">
    <Modal /> {/* Uses dark mode while page stays light */}
  </Theme>
</Theme>

```

The inner `Theme` detects nesting via `ThemeNestingContext` and skips root attribute synchronization.

## Summary

- **Runtime injection** generates CSS dynamically via `useInsertionEffect` when `theme.__built` is undefined, inserting styles into `@layer reset` and `@layer astryx‑theme` blocks.
- **Pre‑built themes** set `__built: true` via the CLI, causing the provider to skip injection and assume static CSS is already loaded.
- **Root synchronization** occurs only for the first `Theme` in the tree, writing `data-theme` and `data-astryx-theme` to the `<html>` element to support browser‑native color schemes.
- **Nested themes** consume context without modifying document attributes, enabling isolated theme overrides.
- The `defineTheme` utility in [`packages/core/src/theme/defineTheme.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/defineTheme.ts) creates theme objects, while the CLI build command generates optimized static assets.

## Frequently Asked Questions

### How does the Theme provider know whether to inject CSS at runtime?

The provider checks the `theme.__built` boolean flag in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx) (line 113). If this flag is present and true, `useThemeStyleInjection` returns immediately without creating style tags. If false or undefined, the hook generates CSS strings and inserts them into the document head via `useInsertionEffect`.

### What is the performance impact of runtime CSS injection?

Runtime injection executes JavaScript to generate and insert styles during the component lifecycle, which can cause layout shifts and increase bundle size. The provider emits a development warning (lines 122‑133 in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx)) recommending pre‑built themes for production, where CSS is loaded via static files and parsed natively by the browser.

### Why does the root Theme sync attributes to the HTML element?

The root provider executes `useRootThemeSync` (lines 182‑214 in [`packages/core/src/theme/Theme.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/theme/Theme.tsx)) to set `data-theme` and `data-astryx-theme` on the `<html>` tag. This enables browser‑native color scheme support for UI elements like scrollbars and form controls, and ensures scoped CSS reaches elements outside the React tree such as portals and modals.

### Can I nest multiple Theme providers in the same application?

Yes. The `Theme` component uses `ThemeNestingContext` to detect when it is rendered inside another `Theme`. Nested providers skip root attribute synchronization and CSS injection if the parent already handled it, while still providing theme context to their descendants. This allows isolated theme overrides for specific components or sections.