← Writing / Design Systems & AI

Creating Design Systems in Figma for AI

My notes and a step-by-step guide for creating machine-readable design systems in Figma

These are my notes for configuring a Figma library so external code agents and AI tools can read and interpret your design system and use it to consistently generate designs. I consider it a WIP as Figma continues updating their UI, but this should be 100% up-to-date as of the publication date.

Defining Your Base Primitives

Before opening Figma, establish a minimum viable token set. Having dozens of unmapped colour stops creates decision overhead for both designers and models.

What You Need to Define

Keep your primitive foundation lean:

  • 1 neutral ramp (10–12 stops): Baseline for canvases, cards, borders, and text.
  • 1 primary brand ramp (10–12 stops): Powers interactive surfaces and focus rings.
  • 3–4 semantic utility ramps (10–12 stops each): Critical (red), warning (amber), success (green), and info (blue/sky).
  • 1 spacing scale (7–9 values): Built on an 8pt/4pt grid (0, 4, 8, 12, 16, 24, 32, 48, 64).
  • 1 corner radius scale (4 values): sm (4px), md (8px), lg (16px), and full (9999px).

Generating vs Importing Base Palettes

Option A: Adopt a Pre-Built Scale (Recommended)

Starting from an established open-source palette avoids extra work like manual contrast testing. A couple of options:

Option B: Generate a Custom Scale in OKLCH

When working with custom brand colours, generate ramps using a perceptually uniform colour space (OKLCH) rather than standard HSL or hex adjustments. This ensures consistent perceived lightness across different hues. Some tools to help with this:

Variable Architecture and Scoping

Create the Primitive Collection (_Primitives)

Goal: Store raw colour values, base spacing units, and typography scales in a private collection so AI tools and designers never access unmapped values directly.

  1. Open a new or existing Figma design library file.
  2. In the left-hand sidebar, open Local variables.
  3. Click the collection dropdown at the top left of the dialog and select Create collection.
  4. Name the collection _Primitives (the leading underscore marks this collection as private).
  5. Add base colour variables:
    • Click Create variable → Color.
    • Add base colours using hierarchical naming (e.g. neutral/0toneutral/900).
  6. Add base dimension variables:
    • Click Create variable → Number.
    • Add base scale numbers (e.g. 4 = 4, 8 = 8, 16 = 16, 24 = 24, 32 = 32).
  7. Open the collection settings menu and toggle Hide from publishing on (shouldn't be necessary with the _ in the name, but just in case!).

Create the Semantic Collection

Goal: Establish a public layer of intent-driven tokens that alias back to _Primitives.

  1. In the Local variables panel, create a second collection named Semantic.
  2. Add semantic colour tokens aliased to primitives, for example:
    • Create a Color variable named surface/default and assign one of primitive colours to it.
  3. Add semantic spacing tokens in a similar way.

Configure Variable Code Syntax

Goal: Map Figma variables to their exact production token names so Dev Mode and export tools output clean token references rather than raw values.

  1. Hover over a variable row in the Semantic collection and click the Edit variable icon in the right column.
  2. Scroll to the Code syntax section and click + Add code syntax.
  3. Select Web.
  4. Enter the CSS custom property name matching your codebase, e.g.:
    • For surface/interactive: var(--color-surface-interactive)
    • For space/default: var(--space-default)
  5. Repeat for each semantic variable.

Configure Variable Scoping

Goal: Restrict where tokens can be applied so models and team members cannot apply text colours to container backgrounds or spacing tokens to corner radii.

  1. In the same Edit Variable dialog, under Scoping, uncheck All properties:
    1. For surface/* variables: Check Frame fill and Shape fill only.
    2. For text/* variables: Check Text fill only.
    3. For space/* variables: Check Gap and Padding only.
  2. Repeat for each semantic variable.

Configure Modes for Context Switching

Goal: Allow downstream code agents to switch themes (e.g. Light/Dark) or density without altering component instances.

  1. In the Semantic collection, click the + (New variable mode) button in the table header.
  2. Rename the default column to Light and the new column to Dark.
  3. In the Dark column, re-alias semantic values to appropriate dark primitives.

Layout Primitives

Build the Stack Primitive (Vertical Container)

Goal: Provide a standardised vertical layout wrapper so models do not invent ad-hoc margins or custom frame wrappers.

  1. Create a frame on the canvas and press Shift + A to enable Auto-layout.
  2. Set direction to Vertical.
  3. Bind padding and gap directly to variables:
    • Click the Horizontal padding field → select variable space/default.
    • Click the Vertical padding field → select variable space/default.
    • Click the Gap between items field → select variable space/default.
  4. Turn the frame into a component (Cmd + Option + K / Ctrl + Alt + K) and name it Primitives/Stack.
  5. Add a variant named gap with values compact, default, section. Link each variant's gap property to its corresponding spacing variable.

Build the Cluster Primitive (Horizontal Wrap)

Goal: Standardise horizontal groupings (toolbars, tag lists, button groups) that wrap predictably.

  1. Create a frame, enable Auto-layout, and set direction to Horizontal.
  2. Set the wrap option to Wrap.
  3. Bind horizontal padding, vertical padding, and gap to space/compact.
  4. Turn into a component named Primitives/Cluster.
  5. Set resizing to Fill container horizontally and Hug contents vertically.

Build the Grid Primitive (Deterministic Layout Columns)

Goal: Prevent agents from estimating fluid column positions with mixed margins.

  1. Create a frame, enable Auto-layout, set direction to Horizontal.
  2. Create inner column frames set to Fill container horizontally and Fill container vertically.
  3. Bind the layout gap to space/default.
  4. Create variants for standard column counts (cols=2, cols=3, cols=4).
  5. Turn into a component named Primitives/Grid.

Core Components

Build Base Components with 1:1 Prop Naming

Goal: Align Figma property names and values exactly with production props so the model does not hallucinate non-existent attributes.

Example: Building a Button

  1. Build a primary button using an Auto-layout frame containing an icon slot and text layer.
  2. Bind fills, strokes, padding, and text colours directly to Semantic collection variables.
  3. Create a component set named Button with the following component properties:
    • Variant property: variant → values: primary, secondary, tertiary, critical.
    • Variant property: size → values: sm, md, lg.
    • Boolean property: isDisabled → values: false (default), true.
    • Instance swap property: leadingIcon → default: select a base system icon; turn off visibility if optional.
    • Text property: label → default: "Button".

Link Dev Resources and Contract Metadata

Goal: Connect the Figma component to its code counterpart using native fields that extraction tools and Dev Mode can read without requiring CLI tools.

  1. Select the component set on the canvas.
  2. In the right-hand properties sidebar, navigate to the Documentation section:
    • In the link field, add your Dev Resource link (the direct URL to the component file in GitHub or your Storybook documentation).
  3. In the Description field, document the component details/constraints directly:
markdown
Import: import { Button } from '@org/design-system';
Props:
- variant: 'primary' | 'secondary' | 'tertiary' | 'critical'
- size: 'sm' | 'md' | 'lg'
- isDisabled: boolean
- isLoading: boolean
- leadingIcon: SystemIcon
- label: string (required)
Constraints:
- Max 1 primary button per view.
- Pair with color.surface.interactive.

Complex Patterns and Slot Architecture

Build Slot-Based Containers (Card Pattern)

Goal: Replace hidden-layer switches with typed instance swap slots, native preferred values, and exposed nested instances so agents compose containers predictably.

  1. Create a generic placeholder component named _Slot:
    • Draw a frame, press Shift + A for Auto-layout, and set resizing to Fill container horizontally and Hug contents vertically.
    • Add a dashed stroke or subtle muted fill, convert to a component (Cmd + Option + K), and toggle Hide from publishing on.
  2. Build the sub-components:
    • Card.Header************************: Auto-layout frame containing a title text layer and a nested instance of Button. Select the button instance, click Create component property (target icon) next to the component name in the right panel, select Instance swap, name it actionSlot, and under Preferred values, click + to add Button and IconButton.
    • Card.Body************************: Auto-layout frame with space/default padding containing an instance of _Slot. Select _Slot, create an Instance swap property named contentSlot.
    • Card.Footer************************: Auto-layout frame containing an instance of Primitives/Cluster. Select the cluster, create an Instance swap property named footerSlot.
  3. Assemble the parent Card container:
    • Place instances of Card.Header, Card.Body, and Card.Footer inside a parent Auto-layout frame.
    • Turn the frame into a component named Card.
  4. Surface nested controls via Nested instances:
    • Select the parent Card component.
    • In the Properties section of the right sidebar, click + → Nested instances.
    • Check Card.Header and Card.Footer. Sub-component properties will now appear directly on the top-level Card properties panel.
  5. In the Card description field, document the slot rules:
markdown
Slot Contract:
- actionSlot: Button (variant: 'ghost' | 'secondary' only)
- contentSlot: Stack, Text, FormField, or Table
- footerSlot: Cluster containing action buttons

Build Row-First Data Tables for DOM and Accessibility Parity

Goal: Mirror row-first DOM structure (<tr>) so code agents do not generate inaccessible, column-first markup that violates screen reader hierarchy (WCAG 1.3.1).

  1. Create the TableCell component:
    • Auto-layout frame with space/compact padding.
    • Add a text layer linked to a text property named value.
    • Add a boolean property named isNumeric (aligns text right when true).
    • Resizing: Horizontal = Fill container, Vertical = Hug contents.
  2. Create the TableHeaderCell component:
    • Same dimensions as TableCell, using text/muted and semi-bold typography.
  3. Build the TableRow component:
    • Create an Auto-layout frame set to Horizontal, padding 0, gap 0, sizing set to Fill container horizontally.
    • Insert multiple instances of TableCell (e.g. 4 cells).
    • Set each cell inside the row to Fill container (for equal distribution) or bind widths to fixed column tokens.
  4. Build the TableHeaderRow component using TableHeaderCell instances with matching column widths.
  5. In the component description, document the composition rule:
markdown
Table Contract:
- Tables must be composed horizontally by row (`TableHeaderRow` followed by `TableRow` instances).
- Cell count in rows must match header cell count.
- Do not use column-based vertical stacks or loose divider lines.

Structure Form Patterns with Exposed Nested Properties

Goal: Construct form controls so models can populate labels, inputs, and validation messages without drilling into child layers.

  1. Create the FormField component:
    • Frame set to vertical Auto-layout with space/compact gap.
    • Top layer: Text layer for the label linked to a text property label.
    • Middle layer: Nested instance of TextInput. Bind the instance to an Instance swap property named inputSlot. Under Preferred values, add TextInput, Select, and Textarea.
    • Bottom layer: Text layer for validation text linked to a text property helpText. Add a boolean property isError (switches text colour to text/critical).
  2. Expose the nested input properties:
    • Select the FormField component.
    • In the right sidebar, click + under Properties → Nested instances → select the inputSlot instance.
    • Selecting FormField on canvas now exposes the input's placeholder and state directly on the primary panel.
  3. In the component description, add:
markdown
Usage: Wraps form controls with accessible labelling and validation.
Constraints:
- Always supply a non-empty `label`.
- Set `isError: true` only when displaying an active validation failure.

Audit, Extraction, and Handoff

Audit the Library for Detached Values

Goal: Confirm all fills, strokes, dimensions, and metadata are linked to tokens before exporting machine manifests.

  1. Audit colour bindings:
    • Select the parent frames on your component pages and check the Selection colours panel in the right sidebar.
    • Verify that every colour swatch displays a semantic variable pill (e.g. surface/default). If a raw hex code appears, rebind it to the appropriate semantic token.
  2. Audit layout spacing:
    • Inspect auto-layout components in Dev Mode (Shift + D) to confirm all padding and gap fields resolve to semantic number variables (e.g. space/default) rather than hardcoded pixel values.
  3. Verify documentation metadata:
    • Confirm every published component set contains a complete Description block (outlining prop contracts and constraints) and a Documentation link pointing to the production repository or Storybook documentation.
    • Confirm every semantic variable has its Code syntax field populated with its matching CSS custom property.

Extract the Manifest

Goal: Convert Figma variables and component contracts into a text manifest for AI code agents.

N.B. This step isn't necessary if you're using MCP to connect your tools.

  1. Export variables:
    • Enterprise plans: Call the Figma Variables REST API (GET /v1/files/:file_key/variables/local) using an access token with file_variables:read permissions.
    • Starter, Professional, and Organisation plans: Use the Figma Plugin API (figma.variables.getLocalVariablesAsync()) or a community plugin (such as Variables to JSON) to export the collection.
  2. Export component contracts:
    • Retrieve component names, descriptions, and properties via the standard Figma Files REST API (GET /v1/files/:file_key).
  3. Assemble the manifest:
    • Combine the exported tokens and component contracts into your project manifest file (e.g. .cursor/rules/design-system.mdc or ai-context.manifest.md):
      • Section 1: Golden rules and boundaries.
      • Section 2: Semantic token registry (with CSS variable aliases).
      • Section 3: Component signatures, imports, and prop types.
      • Section 4: Slot contracts and layout primitives (Stack, Cluster).

Verify with an AI Code Agent

Goal: Validate that an unconstrained model generates production-ready UI when given the extracted manifest.

  1. Place the generated manifest file in the root or rules directory of a test project repository.
  2. In your agent of choice, run a scaffolding prompt:
text
Generate a user profile settings card containing an avatar, name/email header, two form inputs, and a save button. Follow all rules and component signatures in our manifest file.

Inspect the generated output against three validation criteria:

  • Did the agent import components directly from @org/design-system instead of writing custom markup?
  • Are layout wrappers constructed using Stack or Cluster with semantic spacing tokens?
  • Are colours referenced strictly by semantic aliases without arbitrary hex codes or custom utility classes?

Screenshot Image Preview
100%