Loading...
Loading...
Build scalable design systems with design tokens, theming infrastructure, and component architecture patterns. Use when creating design tokens, implementing theme switching, building component libraries, or establishing design system foundations.
npx skill4agent add wshobson/agents design-system-patterns// Design tokens with CSS custom properties
const tokens = {
colors: {
// Primitive tokens
gray: {
50: "#fafafa",
100: "#f5f5f5",
900: "#171717",
},
blue: {
500: "#3b82f6",
600: "#2563eb",
},
},
// Semantic tokens (reference primitives)
semantic: {
light: {
"text-primary": "var(--color-gray-900)",
"text-secondary": "var(--color-gray-600)",
"surface-default": "var(--color-white)",
"surface-elevated": "var(--color-gray-50)",
"border-default": "var(--color-gray-200)",
"interactive-primary": "var(--color-blue-500)",
},
dark: {
"text-primary": "var(--color-gray-50)",
"text-secondary": "var(--color-gray-400)",
"surface-default": "var(--color-gray-900)",
"surface-elevated": "var(--color-gray-800)",
"border-default": "var(--color-gray-700)",
"interactive-primary": "var(--color-blue-400)",
},
},
};/* Layer 1: Primitive tokens (raw values) */
:root {
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-gray-50: #fafafa;
--color-gray-900: #171717;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
}
/* Layer 2: Semantic tokens (meaning) */
:root {
--text-primary: var(--color-gray-900);
--text-secondary: var(--color-gray-600);
--surface-default: white;
--interactive-primary: var(--color-blue-500);
--interactive-primary-hover: var(--color-blue-600);
}
/* Layer 3: Component tokens (specific usage) */
:root {
--button-bg: var(--interactive-primary);
--button-bg-hover: var(--interactive-primary-hover);
--button-text: white;
--button-radius: var(--radius-md);
--button-padding-x: var(--space-4);
--button-padding-y: var(--space-2);
}import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
interface ThemeContextValue {
theme: Theme;
resolvedTheme: "light" | "dark";
setTheme: (theme: Theme) => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
if (typeof window !== "undefined") {
return (localStorage.getItem("theme") as Theme) || "system";
}
return "system";
});
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
useEffect(() => {
const root = document.documentElement;
const applyTheme = (isDark: boolean) => {
root.classList.remove("light", "dark");
root.classList.add(isDark ? "dark" : "light");
setResolvedTheme(isDark ? "dark" : "light");
};
if (theme === "system") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
applyTheme(mediaQuery.matches);
const handler = (e: MediaQueryListEvent) => applyTheme(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
} else {
applyTheme(theme === "dark");
}
}, [theme]);
useEffect(() => {
localStorage.setItem("theme", theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be used within ThemeProvider");
return context;
};import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
// Base styles
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
sm: "h-9 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-11 px-8 text-base",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
},
);
interface ButtonProps
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}// style-dictionary.config.js
module.exports = {
source: ["tokens/**/*.json"],
platforms: {
css: {
transformGroup: "css",
buildPath: "dist/css/",
files: [
{
destination: "variables.css",
format: "css/variables",
options: {
outputReferences: true, // Preserve token references
},
},
],
},
scss: {
transformGroup: "scss",
buildPath: "dist/scss/",
files: [
{
destination: "_variables.scss",
format: "scss/variables",
},
],
},
ios: {
transformGroup: "ios-swift",
buildPath: "dist/ios/",
files: [
{
destination: "DesignTokens.swift",
format: "ios-swift/class.swift",
className: "DesignTokens",
},
],
},
android: {
transformGroup: "android",
buildPath: "dist/android/",
files: [
{
destination: "colors.xml",
format: "android/colors",
filter: { attributes: { category: "color" } },
},
],
},
},
};