React Integration
This guide shows you how to integrate the Axiomatic Color into a React application using TypeScript.
1. Setup
Section titled “1. Setup”First, install the CLI and generate your theme CSS and TypeScript definitions.
npm install -D @axiomatic-design/colornpx axiomatic initnpx axiomatic build --out ./src/theme.cssnpx axiomatic export --format typescript --out ./src/theme.tsImport the CSS in your root entry point (e.g., main.tsx or App.tsx):
import "./theme.css";2. The Surface Component
Section titled “2. The Surface Component”The core concept of the system is the Surface. In React, we can create a reusable <Surface> component that handles the nesting logic automatically.
import React from "react";import { tokens } from "../theme";
type SurfaceProps = { as?: React.ElementType; variant: keyof typeof tokens.surface; children: React.ReactNode; className?: string;} & React.HTMLAttributes<HTMLElement>;
export function Surface({ as: Component = "div", variant, children, className = "", ...props}: SurfaceProps) { const surfaceClass = tokens.surface[variant];
return ( <Component className={`${surfaceClass} ${className}`} {...props}> {children} </Component> );}3. The Theme Context
Section titled “3. The Theme Context”To handle Dark Mode and System preferences robustly, we recommend creating a Context that wraps the ThemeManager.
import React, { createContext, useContext, useEffect, useState } from "react";import { ThemeManager, type ThemeMode } from "@axiomatic-design/color/browser";
type ThemeContextType = { mode: ThemeMode; setMode: (mode: ThemeMode) => void; toggle: () => void;};
const ThemeContext = createContext<ThemeContextType | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) { // Initialize the manager once const [manager] = useState(() => new ThemeManager()); const [mode, setModeState] = useState<ThemeMode>("system");
useEffect(() => { // Restore saved preference on mount const saved = localStorage.getItem("theme") as ThemeMode | null; if (saved) { manager.setMode(saved); setModeState(saved); } }, [manager]);
const setMode = (newMode: ThemeMode) => { manager.setMode(newMode); setModeState(newMode); localStorage.setItem("theme", newMode); };
const toggle = () => { const next = manager.resolvedMode === "light" ? "dark" : "light"; setMode(next); };
return ( <ThemeContext.Provider value={{ mode, setMode, toggle }}> {children} </ThemeContext.Provider> );}
export function useTheme() { const context = useContext(ThemeContext); if (!context) throw new Error("useTheme must be used within a ThemeProvider"); return context;}4. Usage Example
Section titled “4. Usage Example”Wrap your application in the ThemeProvider, and then use the useTheme hook to control the theme.
import { Surface } from "./components/Surface";import { ThemeProvider, useTheme } from "./context/ThemeContext";import { tokens } from "./theme";
function Header() { const { toggle, mode } = useTheme();
return ( <header style={{ display: "flex", justifyContent: "space-between" }}> <h1 style={{ color: tokens.context.text.high }}>My App</h1> <button onClick={toggle}>{mode === "light" ? "🌙" : "☀️"}</button> </header> );}
export default function App() { return ( <ThemeProvider> <Surface variant="page" style={{ minHeight: "100vh", padding: "2rem" }}> <Header />
<main> <Surface variant="card" style={{ padding: "2rem", borderRadius: "8px" }} > <h2 style={{ color: tokens.context.text.high }}>Hello World</h2> <p style={{ color: tokens.context.text.subtle }}> This card is automatically themed based on its context. </p> </Surface> </main> </Surface> </ThemeProvider> );}