Skip to content

Custom Framework Integration

If we don’t have a guide for your specific framework, don’t worry! The Axiomatic Color system is designed to be framework-agnostic. This guide covers the core concepts and APIs you need to build your own integration.

There are three main pieces you need to implement:

  1. Theme Management: Handling light/dark mode and system preferences.
  2. Surfaces: A component to apply surface styles and nest contexts.
  3. Tokens: Accessing the design tokens in your components.

The library provides a ThemeManager class that handles the complexity of:

  • Detecting system color preferences.
  • Listening for changes (e.g., when the user changes their OS theme).
  • Syncing the meta[name="theme-color"] tag.
  • Updating the data-theme attribute on the root element.
import { ThemeManager, type ThemeMode } from "@axiomatic-design/color/browser";
// 1. Initialize
const manager = new ThemeManager();
// 2. Get current state
console.log(manager.mode); // 'light' | 'dark' | 'system'
console.log(manager.resolvedMode); // 'light' | 'dark' (resolves 'system')
// 3. Change mode
manager.setMode("dark");
// 4. Cleanup (if needed)
manager.dispose();

Most frameworks have a way to share state (Context in React, Stores in Svelte, Services in Ember). You should wrap the ThemeManager in your framework’s state management primitive.

Requirements for your wrapper:

  1. Persistence: The ThemeManager does not handle localStorage. You should read/write to localStorage in your wrapper.
  2. Reactivity: The ThemeManager is imperative. You need to expose its state (mode and resolvedMode) in a reactive way for your UI to update.

The “Surface” is the fundamental building block. It does two things:

  1. Sets the background-color and color for its container.
  2. Establishes a new “context” for nested content (e.g., adjusting contrast).

Your framework likely has a way to render a component with a dynamic class.

import { tokens } from "./your-generated-theme";
// Pseudo-code for a Surface component
function Surface({ variant, as = "div", children }) {
// 1. Get the class name for the variant (e.g., "surface-card")
const className = tokens.surface[variant];
// 2. Render the element
return <as className={className}>{children}</as>;
}

When you run axiomatic export, you generate a TypeScript file containing your tokens.

Terminal window
npx axiomatic export --format typescript --out src/theme.ts

This file exports a tokens object that mirrors your CSS variables.

import { tokens } from "./theme";
// Usage in styles
const style = {
color: tokens.context.text.high, // "var(--text-high-token)"
};

You can use the CSS variables directly (e.g., var(--text-high)), but the JS tokens provide:

  • Type Safety: TypeScript will error if you use a token that doesn’t exist.
  • Refactoring: Renaming a token in your config will cause build errors where it’s used.
  • Autocomplete: Your IDE will suggest available tokens.

To have a complete integration, ensure you have:

  • Generated theme.css and imported it globally.
  • Generated theme.ts for type-safe token access.
  • Created a ThemeService / ThemeContext wrapping ThemeManager.
  • Created a <Surface> component.
  • (Optional) Added a theme toggle UI.