# How Microsoft Ontology Playground Implements Its Theme System Using CSS Custom Properties

> Discover how Microsoft Ontology Playground uses CSS custom properties for its theme system. Learn about overriding variables and instant theme switching for a dynamic UI.

- Repository: [Microsoft/Ontology-Playground](https://github.com/microsoft/Ontology-Playground)
- Tags: internals
- Published: 2026-07-23

---

**Microsoft's Ontology Playground uses a CSS custom properties architecture where default variables are defined in `:root` and overridden by theme-specific classes (`.light-theme`, `.theme-aurora`, `.theme-crimson`) applied to the document root, enabling instant theme switching without JavaScript re-renders.**

The Ontology Playground repository demonstrates a robust implementation of a theme system using CSS custom properties that enables multiple color schemes with minimal runtime overhead. By centralizing design tokens in global CSS variables and toggling classes on the `<html>` element, the application achieves immediate visual updates across all components. This article examines the exact implementation patterns found in the Microsoft/Ontology-Playground codebase, including the variable definitions in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) and the React-based theme switching logic in [`src/components/Header.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/components/Header.tsx).

## Architecture: Default Variables and Theme Overrides

The theme system relies on a two-layer architecture: **base variables** defined in the global scope and **override blocks** that selectively replace variables when specific classes are present.

### Defining Base Variables in :root

The default dark theme is established in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) within the `:root` selector (lines 3-62). These CSS custom properties serve as the single source of truth for colors, shadows, and spacing values that components consume.

```css
:root {
  --bg-primary: #1B1B1B;
  --bg-secondary: #2D2D2D;
  --text-primary: #FFFFFF;
  --text-secondary: #A0A0A0;
  --ms-blue: #0078D4;
  --on-accent: #FFFFFF;
  /* ... additional variables ... */
}

```

Components reference these variables using the `var(--property)` syntax, ensuring they inherit the active theme's values automatically.

### Theme-Specific Override Classes

Alternative themes are implemented as CSS classes that override specific variables. The **light theme** uses the `.light-theme` class (lines 82-106 in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css)), while custom branded themes like **Aurora** and **Crimson** use specialized classes.

```css
.light-theme {
  --bg-primary: #F5F5F5;
  --bg-secondary: #FFFFFF;
  --text-primary: #1A1A1A;
  --text-secondary: #616161;
  --ms-blue: #0078D4;
  --on-accent: #FFFFFF;
}

.theme-aurora {
  --bg-primary: #0E2B27;
  --bg-secondary: #153930;
  --text-primary: #E6F0ED;
  --ms-blue: #2AAA92;
  --on-accent: #0E2B27;
}

.theme-crimson {
  --bg-primary: #F8F9FA;
  --bg-secondary: #FFFFFF;
  --text-primary: #D6002A;
  /* ... crimson-specific overrides ... */
}

```

When any of these classes are applied to the document root, CSS automatically recalculates all `var()` references, updating the entire UI without React re-renders.

## Runtime Theme Switching Implementation

The dynamic switching mechanism is handled by React components that manipulate the document's `classList`. In [`src/components/Header.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/components/Header.tsx) (lines 170-200), the theme picker UI connects user interaction to the DOM.

```tsx
// From Header.tsx theme picker implementation
const applyTheme = (themeId: string) => {
  const root = document.documentElement;
  
  // Remove existing theme classes
  root.classList.remove('light-theme', 'theme-aurora', 'theme-crimson');
  
  // Apply new theme class (except for default dark)
  if (themeId === 'light') {
    root.classList.add('light-theme');
  } else if (themeId !== 'dark') {
    root.classList.add(`theme-${themeId}`);
  }
};

```

A Zustand store manages the current theme state and persists selections to `localStorage`. On initial load, the application reads the stored preference and applies the corresponding class before React hydrates the UI.

## Adding Custom Themes to the System

Extending the theme system requires only CSS additions and minimal TypeScript updates. To implement a "forest" theme with green-tinted aesthetics:

First, add the CSS override block in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css):

```css
.theme-forest {
  --bg-primary: #0A2E1F;
  --bg-secondary: #124B31;
  --text-primary: #E0F2E9;
  --ms-blue: #2E8B57;
  --on-accent: #0A2E1F;
}

```

Then extend the theme options array in [`src/components/Header.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/components/Header.tsx):

```tsx
const themeOptions = [
  { id: 'dark', name: 'Dark', swatch: '#1B1B1B' },
  { id: 'light', name: 'Light', swatch: '#F5F5F5' },
  { id: 'aurora', name: 'Aurora', swatch: '#2AAA92' },
  { id: 'crimson', name: 'Crimson', swatch: '#D6002A' },
  { id: 'forest', name: 'Forest', swatch: '#2E8B57' }  // New entry
];

```

The Zustand store automatically handles the class application:

```tsx
// themeStore.ts
export const useThemeStore = create(set => ({
  theme: 'dark',
  setTheme: (theme: string) => {
    const root = document.documentElement;
    root.classList.remove('light-theme', 'theme-aurora', 'theme-crimson', 'theme-forest');
    
    if (theme !== 'dark') {
      const className = theme === 'light' ? 'light-theme' : `theme-${theme}`;
      root.classList.add(className);
    }
    
    localStorage.setItem('theme', theme);
    set({ theme });
  },
}));

```

## Component Consumption Patterns

Components remain theme-agnostic by referencing CSS variables instead of hard-coded values. This decouples visual presentation from theme logic.

```tsx
// ExampleButton.tsx
export const ExampleButton = () => (
  <button className="btn-primary">
    Themed Button
  </button>
);

```

The corresponding CSS in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) uses the variables:

```css
.btn-primary {
  background-color: var(--ms-blue);
  color: var(--on-accent);
  border: 1px solid var(--bg-secondary);
}

```

When the theme changes, the browser's CSS engine recalculates these values instantly, requiring no JavaScript intervention or component updates.

## Summary

- **Centralized variables**: All design tokens are defined in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) within `:root` and theme-specific class blocks.
- **Class-based switching**: Themes are applied by adding classes like `.light-theme` or `.theme-aurora` to `document.documentElement`.
- **Runtime persistence**: The Zustand store in [`src/components/Header.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/components/Header.tsx) manages state and persists selections to `localStorage`.
- **Zero-component changes**: UI components consume `var(--property)` references, making them automatically compatible with any current or future theme.
- **Extensible architecture**: New themes require only CSS additions and theme option entries, with no changes to component logic.

## Frequently Asked Questions

### How does the theme system handle initial page loads?

The application checks `localStorage` for a saved theme preference during initialization. If found, it applies the corresponding class to the `<html>` element immediately; otherwise, it falls back to the default dark theme defined in `:root`. This happens before React hydration to prevent flash-of-unthemed-content (FOUT).

### Can I add a new theme without modifying React components?

Yes, but with limitations. You can define new CSS classes like `.theme-custom` in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) that override variables, and users could manually add that class to the HTML element. However, to make the theme selectable through the UI, you must add an entry to the `themeOptions` array in [`src/components/Header.tsx`](https://github.com/microsoft/Ontology-Playground/blob/main/src/components/Header.tsx) so the theme picker displays the new option.

### Why does the implementation use CSS custom properties instead of CSS-in-JS?

CSS custom properties provide **immediate** updates across all components without requiring React re-renders, as the browser's CSS engine handles variable recalculation natively. This approach also keeps component files free from styling logic and allows themes to be defined purely in stylesheets, following separation of concerns principles as seen in the [`app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/app.css) structure.

### Where are the color values for the Aurora and Crimson themes defined?

The Aurora theme variables are defined in [`src/styles/app.css`](https://github.com/microsoft/Ontology-Playground/blob/main/src/styles/app.css) at lines 120-155 within the `.theme-aurora` selector block, featuring a deep teal palette (`--bg-primary: #0E2B27`). The Crimson theme follows at lines 158-191 in the `.theme-crimson` block, utilizing Microsoft’s crimson accent colors alongside light backgrounds.