Generated banner for Design System Patterns
Generated logo for Design System Patterns

Design System Patterns

Seth HobsonCommunityCreated by Seth Hobson

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

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.
— the description by Seth Hobson

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a comprehensive framework for building and maintaining scalable design systems. It covers the essential architecture required to bridge the gap between design and development, focusing on token hierarchies, robust theming infrastructure, and flexible component patterns. By establishing a single source of truth, teams can ensure consistency across web and mobile applications while maintaining the ability to evolve their UI foundations.

What it does

  • Defines hierarchical design token structures (primitive, semantic, component).
  • Provides patterns for multi-brand and light/dark mode theming.
  • Outlines advanced component architecture like compound and polymorphic patterns.
  • Offers guidance on token pipelines and Figma-to-code synchronization.
  • Establishes best practices for documentation and versioning of design systems.

When to use it

  • When establishing the foundational visual language for a new application.
  • When refactoring inconsistent UI styles into a centralized design system.
  • When implementing complex theming requirements like dark mode or multi-brand support.
  • When building a reusable component library that needs to scale across multiple teams.
  • When automating the synchronization of design decisions from Figma to code.

Advantages

  • Ensures visual consistency across large-scale applications.
  • Reduces maintenance overhead through semantic token naming.
  • Enables flexible component composition via headless and compound patterns.
  • Supports seamless theme switching and system preference adaptation.
  • Facilitates better collaboration between designers and developers.

Usage examples

“How should I structure my design tokens to support both light and dark modes?”
The agent provides a hierarchical approach using primitive tokens for raw values and semantic tokens that reference those primitives, demonstrating how to swap them based on theme context.
“Explain the compound component pattern for an accordion.”
The agent generates a React example using context to share state between an Accordion root, Item, Trigger, and Content components, ensuring a flexible and accessible API.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/wshobson/design-system-patterns/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip design-system-patterns.zip -d .agents/skills

Documentation by Seth Hobson

Design System Patterns

Master design system architecture to create consistent, maintainable, and scalable UI foundations across web and mobile applications.

When to Use This Skill

  • Creating design tokens for colors, typography, spacing, and shadows

  • Implementing light/dark theme switching with CSS custom properties

  • Building multi-brand theming systems

  • Architecting component libraries with consistent APIs

  • Establishing design-to-code workflows with Figma tokens

  • Creating semantic token hierarchies (primitive, semantic, component)

  • Setting up design system documentation and guidelines

Core Capabilities

1. Design Tokens
  • Primitive tokens (raw values: colors, sizes, fonts)

  • Semantic tokens (contextual meaning: text-primary, surface-elevated)

  • Component tokens (specific usage: button-bg, card-border)

  • Token naming conventions and organization

  • Multi-platform token generation (CSS, iOS, Android)

2. Theming Infrastructure
  • CSS custom properties architecture

  • Theme context providers in React

  • Dynamic theme switching

  • System preference detection (prefers-color-scheme)

  • Persistent theme storage

  • Reduced motion and high contrast modes

3. Component Architecture
  • Compound component patterns

  • Polymorphic components (as prop)

  • Variant and size systems

  • Slot-based composition

  • Headless UI patterns

  • Style props and responsive variants

4. Token Pipeline
  • Figma to code synchronization

  • Style Dictionary configuration

  • Token transformation and formatting

  • CI/CD integration for token updates

Quick Start

// 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)",
    },
  },
};

Detailed patterns and worked examples

Detailed pattern documentation lives in references/details.md. Read that file when the navigation tier above is insufficient.

Best Practices

  1. Name Tokens by Purpose: Use semantic names (text-primary) not visual descriptions (dark-gray)

  2. Maintain Token Hierarchy: Primitives > Semantic > Component tokens

  3. Document Token Usage: Include usage guidelines with token definitions

  4. Version Tokens: Treat token changes as API changes with semver

  5. Test Theme Combinations: Verify all themes work with all components

  6. Automate Token Pipeline: CI/CD for Figma-to-code synchronization

  7. Provide Migration Paths: Deprecate tokens gradually with clear alternatives

Common Issues

  • Token Sprawl: Too many tokens without clear hierarchy

  • Inconsistent Naming: Mixed conventions (camelCase vs kebab-case)

  • Missing Dark Mode: Tokens that don't adapt to theme changes

  • Hardcoded Values: Using raw values instead of tokens

  • Circular References: Tokens referencing each other in loops

  • Platform Gaps: Tokens missing for some platforms (web but not mobile)

Files

references/component-architecture.md

Component Architecture Patterns

Overview

Well-architected components are reusable, composable, and maintainable. This guide covers patterns for building flexible component APIs that scale across design systems.

Compound Components

Compound components share implicit state through React context, allowing flexible composition.

// Compound component pattern
import * as React from "react";

interface AccordionContextValue {
  openItems: Set<string>;
  toggle: (id: string) => void;
  type: "single" | "multiple";
}

const AccordionContext = React.createContext<AccordionContextValue | null>(
  null,
);

function useAccordionContext() {
  const context = React.useContext(AccordionContext);
  if (!context) {
    throw new Error("Accordion components must be used within an Accordion");
  }
  return context;
}

// Root component
interface AccordionProps {
  children: React.ReactNode;
  type?: "single" | "multiple";
  defaultOpen?: string[];
}

function Accordion({
  children,
  type = "single",
  defaultOpen = [],
}: AccordionProps) {
  const [openItems, setOpenItems] = React.useState<Set<string>>(
    new Set(defaultOpen),
  );

  const toggle = React.useCallback(
    (id: string) => {
      setOpenItems((prev) => {
        const next = new Set(prev);
        if (next.has(id)) {
          next.delete(id);
        } else {
          if (type === "single") {
            next.clear();
          }
          next.add(id);
        }
        return next;
      });
    },
    [type],
  );

  return (
    <AccordionContext.Provider value={{ openItems, toggle, type }}>
      <div className="divide-y divide-border">{children}</div>
    </AccordionContext.Provider>
  );
}

// Item component
interface AccordionItemProps {
  children: React.ReactNode;
  id: string;
}

function AccordionItem({ children, id }: AccordionItemProps) {
  return (
    <AccordionItemContext.Provider value={{ id }}>
      <div className="py-2">{children}</div>
    </AccordionItemContext.Provider>
  );
}

// Trigger component
function AccordionTrigger({ children }: { children: React.ReactNode }) {
  const { toggle, openItems } = useAccordionContext();
  const { id } = useAccordionItemContext();
  const isOpen = openItems.has(id);

  return (
    <button
      onClick={() => toggle(id)}
      className="flex w-full items-center justify-between py-2 font-medium"
      aria-expanded={isOpen}
    >
      {children}
      <ChevronDown
        className={`h-4 w-4 transition-transform ${isOpen ? "rotate-180" : ""}`}
      />
    </button>
  );
}

// Content component
function AccordionContent({ children }: { children: React.ReactNode }) {
  const { openItems } = useAccordionContext();
  const { id } = useAccordionItemContext();
  const isOpen = openItems.has(id);

  if (!isOpen) return null;

  return <div className="pb-4 text-muted-foreground">{children}</div>;
}

// Export compound component
export const AccordionCompound = Object.assign(Accordion, {
  Item: AccordionItem,
  Trigger: AccordionTrigger,
  Content: AccordionContent,
});

// Usage
function Example() {
  return (
    <AccordionCompound type="single" defaultOpen={["item-1"]}>
      <AccordionCompound.Item id="item-1">
        <AccordionCompound.Trigger>Is it accessible?</AccordionCompound.Trigger>
        <AccordionCompound.Content>
          Yes. It follows WAI-ARIA patterns.
        </AccordionCompound.Content>
      </AccordionCompound.Item>
      <AccordionCompound.Item id="item-2">
        <AccordionCompound.Trigger>Is it styled?</AccordionCompound.Trigger>
        <AccordionCompound.Content>
          Yes. It uses Tailwind CSS.
        </AccordionCompound.Content>
      </AccordionCompound.Item>
    </AccordionCompound>
  );
}

Polymorphic Components

Polymorphic components can render as different HTML elements or other components.

// Polymorphic component with proper TypeScript support
import * as React from "react";

type AsProp<C extends React.ElementType> = {
  as?: C;
};

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);

type PolymorphicComponentProp<
  C extends React.ElementType,
  Props = {},
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

type PolymorphicRef<C extends React.ElementType> =
  React.ComponentPropsWithRef<C>["ref"];

type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props = {},
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> };

// Button component
interface ButtonOwnProps {
  variant?: "default" | "outline" | "ghost";
  size?: "sm" | "md" | "lg";
}

type ButtonProps<C extends React.ElementType = "button"> =
  PolymorphicComponentPropWithRef<C, ButtonOwnProps>;

const Button = React.forwardRef(
  <C extends React.ElementType = "button">(
    {
      as,
      variant = "default",
      size = "md",
      className,
      children,
      ...props
    }: ButtonProps<C>,
    ref?: PolymorphicRef<C>,
  ) => {
    const Component = as || "button";

    const variantClasses = {
      default: "bg-primary text-primary-foreground hover:bg-primary/90",
      outline: "border border-input bg-background hover:bg-accent",
      ghost: "hover:bg-accent hover:text-accent-foreground",
    };

    const sizeClasses = {
      sm: "h-8 px-3 text-sm",
      md: "h-10 px-4 text-sm",
      lg: "h-12 px-6 text-base",
    };

    return (
      <Component
        ref={ref}
        className={cn(
          "inline-flex items-center justify-center rounded-md font-medium transition-colors",
          variantClasses[variant],
          sizeClasses[size],
          className,
        )}
        {...props}
      >
        {children}
      </Component>
    );
  },
);

Button.displayName = "Button";

// Usage
function Example() {
  return (
    <>
      {/* As button (default) */}
      <Button variant="default" onClick={() => {}}>
        Click me
      </Button>

      {/* As anchor link */}
      <Button as="a" href="/page" variant="outline">
        Go to page
      </Button>

      {/* As Next.js Link */}
      <Button as={Link} href="/dashboard" variant="ghost">
        Dashboard
      </Button>
    </>
  );
}

Slot Pattern

Slots allow users to replace default elements with custom implementations.

// Slot pattern for customizable components
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  asChild?: boolean;
  variant?: "default" | "outline";
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ asChild = false, variant = "default", className, ...props }, ref) => {
    const Comp = asChild ? Slot : "button";

    return (
      <Comp
        ref={ref}
        className={cn(
          "inline-flex items-center justify-center rounded-md font-medium",
          variant === "default" && "bg-primary text-primary-foreground",
          variant === "outline" && "border border-input bg-background",
          className,
        )}
        {...props}
      />
    );
  },
);

// Usage - Button styles applied to child element
function Example() {
  return (
    <Button asChild variant="outline">
      <a href="/link">I'm a link that looks like a button</a>
    </Button>
  );
}

Headless Components

Headless components provide behavior without styling, enabling complete visual customization.

// Headless toggle hook
import * as React from "react";

interface UseToggleProps {
  defaultPressed?: boolean;
  pressed?: boolean;
  onPressedChange?: (pressed: boolean) => void;
}

function useToggle({
  defaultPressed = false,
  pressed: controlledPressed,
  onPressedChange,
}: UseToggleProps = {}) {
  const [uncontrolledPressed, setUncontrolledPressed] =
    React.useState(defaultPressed);

  const isControlled = controlledPressed !== undefined;
  const pressed = isControlled ? controlledPressed : uncontrolledPressed;

  const toggle = React.useCallback(() => {
    if (!isControlled) {
      setUncontrolledPressed((prev) => !prev);
    }
    onPressedChange?.(!pressed);
  }, [isControlled, pressed, onPressedChange]);

  return {
    pressed,
    toggle,
    buttonProps: {
      role: "switch" as const,
      "aria-checked": pressed,
      onClick: toggle,
    },
  };
}

// Headless listbox hook
interface UseListboxProps<T> {
  items: T[];
  defaultSelectedIndex?: number;
  onSelect?: (item: T, index: number) => void;
}

function useListbox<T>({
  items,
  defaultSelectedIndex = -1,
  onSelect,
}: UseListboxProps<T>) {
  const [selectedIndex, setSelectedIndex] =
    React.useState(defaultSelectedIndex);
  const [highlightedIndex, setHighlightedIndex] = React.useState(-1);

  const select = React.useCallback(
    (index: number) => {
      setSelectedIndex(index);
      onSelect?.(items[index], index);
    },
    [items, onSelect],
  );

  const handleKeyDown = React.useCallback(
    (event: React.KeyboardEvent) => {
      switch (event.key) {
        case "ArrowDown":
          event.preventDefault();
          setHighlightedIndex((prev) =>
            prev < items.length - 1 ? prev + 1 : prev,
          );
          break;
        case "ArrowUp":
          event.preventDefault();
          setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev));
          break;
        case "Enter":
        case " ":
          event.preventDefault();
          if (highlightedIndex >= 0) {
            select(highlightedIndex);
          }
          break;
        case "Home":
          event.preventDefault();
          setHighlightedIndex(0);
          break;
        case "End":
          event.preventDefault();
          setHighlightedIndex(items.length - 1);
          break;
      }
    },
    [items.length, highlightedIndex, select],
  );

  return {
    selectedIndex,
    highlightedIndex,
    select,
    setHighlightedIndex,
    listboxProps: {
      role: "listbox" as const,
      tabIndex: 0,
      onKeyDown: handleKeyDown,
    },
    getOptionProps: (index: number) => ({
      role: "option" as const,
      "aria-selected": index === selectedIndex,
      onClick: () => select(index),
      onMouseEnter: () => setHighlightedIndex(index),
    }),
  };
}

Variant System with CVA

Class Variance Authority (CVA) provides type-safe variant management.

import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

// Define variants
const badgeVariants = cva(
  // Base classes
  'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',
  {
    variants: {
      variant: {
        default: 'border-transparent bg-primary text-primary-foreground',
        secondary: 'border-transparent bg-secondary text-secondary-foreground',
        destructive: 'border-transparent bg-destructive text-destructive-foreground',
        outline: 'text-foreground',
        success: 'border-transparent bg-green-500 text-white',
        warning: 'border-transparent bg-amber-500 text-white',
      },
      size: {
        sm: 'text-xs px-2 py-0.5',
        md: 'text-sm px-2.5 py-0.5',
        lg: 'text-sm px-3 py-1',
      },
    },
    compoundVariants: [
      // Outline variant with sizes
      {
        variant: 'outline',
        size: 'lg',
        className: 'border-2',
      },
    ],
    defaultVariants: {
      variant: 'default',
      size: 'md',
    },
  }
);

// Component with variants
interface BadgeProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, size, ...props }: BadgeProps) {
  return (
    <div className={cn(badgeVariants({ variant, size, className }))} {...props} />
  );
}

// Usage
<Badge variant="success" size="lg">Active</Badge>
<Badge variant="destructive">Error</Badge>
<Badge variant="outline">Draft</Badge>

Responsive Variants

import { cva } from "class-variance-authority";

// Responsive variant configuration
const containerVariants = cva("mx-auto w-full px-4", {
  variants: {
    size: {
      sm: "max-w-screen-sm",
      md: "max-w-screen-md",
      lg: "max-w-screen-lg",
      xl: "max-w-screen-xl",
      full: "max-w-full",
    },
    padding: {
      none: "px-0",
      sm: "px-4 md:px-6",
      md: "px-4 md:px-8 lg:px-12",
      lg: "px-6 md:px-12 lg:px-20",
    },
  },
  defaultVariants: {
    size: "lg",
    padding: "md",
  },
});

// Responsive prop pattern
interface ResponsiveValue<T> {
  base?: T;
  sm?: T;
  md?: T;
  lg?: T;
  xl?: T;
}

function getResponsiveClasses<T extends string>(
  prop: T | ResponsiveValue<T> | undefined,
  classMap: Record<T, string>,
  responsiveClassMap: Record<string, Record<T, string>>,
): string {
  if (!prop) return "";

  if (typeof prop === "string") {
    return classMap[prop];
  }

  return Object.entries(prop)
    .map(([breakpoint, value]) => {
      if (breakpoint === "base") {
        return classMap[value as T];
      }
      return responsiveClassMap[breakpoint]?.[value as T];
    })
    .filter(Boolean)
    .join(" ");
}

Composition Patterns

Render Props
interface DataListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  renderEmpty?: () => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function DataList<T>({
  items,
  renderItem,
  renderEmpty,
  keyExtractor,
}: DataListProps<T>) {
  if (items.length === 0 && renderEmpty) {
    return <>{renderEmpty()}</>;
  }

  return (
    <ul className="space-y-2">
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>{renderItem(item, index)}</li>
      ))}
    </ul>
  );
}

// Usage
<DataList
  items={users}
  keyExtractor={(user) => user.id}
  renderItem={(user) => <UserCard user={user} />}
  renderEmpty={() => <EmptyState message="No users found" />}
/>;
Children as Function
interface DisclosureProps {
  children: (props: { isOpen: boolean; toggle: () => void }) => React.ReactNode;
  defaultOpen?: boolean;
}

function Disclosure({ children, defaultOpen = false }: DisclosureProps) {
  const [isOpen, setIsOpen] = React.useState(defaultOpen);
  const toggle = () => setIsOpen((prev) => !prev);

  return <>{children({ isOpen, toggle })}</>;
}

// Usage
<Disclosure>
  {({ isOpen, toggle }) => (
    <>
      <button onClick={toggle}>{isOpen ? "Close" : "Open"}</button>
      {isOpen && <div>Content</div>}
    </>
  )}
</Disclosure>;

Best Practices

  1. Prefer Composition: Build complex components from simple primitives

  2. Use Controlled/Uncontrolled Pattern: Support both modes for flexibility

  3. Forward Refs: Always forward refs to root elements

  4. Spread Props: Allow custom props to pass through

  5. Provide Defaults: Set sensible defaults for optional props

  6. Type Everything: Use TypeScript for prop validation

  7. Document Variants: Show all variant combinations in Storybook

  8. Test Accessibility: Verify keyboard navigation and screen reader support

Resources

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Seth Hobson
Publisher
Seth Hobson (Community)
Source
wshobson/agents · plugins/ui-design/skills/design-system-patterns · commit 4bde316
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Seth Hobson.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download design-system-patterns.zip