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), andfull(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.
- Open a new or existing Figma design library file.
- In the left-hand sidebar, open Local variables.
- Click the collection dropdown at the top left of the dialog and select Create collection.
- Name the collection
_Primitives(the leading underscore marks this collection as private). - Add base colour variables:
- Click Create variable → Color.
- Add base colours using hierarchical naming (e.g.
neutral/0toneutral/900).
- Add base dimension variables:
- Click Create variable → Number.
- Add base scale numbers (e.g.
4=4,8=8,16=16,24=24,32=32).
- 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.
- In the Local variables panel, create a second collection named
Semantic. - Add semantic colour tokens aliased to primitives, for example:
- Create a Color variable named
surface/defaultand assign one of primitive colours to it.
- Create a Color variable named
- 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.
- Hover over a variable row in the
Semanticcollection and click the Edit variable icon in the right column. - Scroll to the Code syntax section and click + Add code syntax.
- Select Web.
- Enter the CSS custom property name matching your codebase, e.g.:
- For
surface/interactive:var(--color-surface-interactive) - For
space/default:var(--space-default)
- For
- 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.
- In the same Edit Variable dialog, under Scoping, uncheck All properties:
- For
surface/*variables: Check Frame fill and Shape fill only. - For
text/*variables: Check Text fill only. - For
space/*variables: Check Gap and Padding only.
- For
- 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.
- In the
Semanticcollection, click the + (New variable mode) button in the table header. - Rename the default column to
Lightand the new column toDark. - In the
Darkcolumn, 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.
- Create a frame on the canvas and press
Shift + Ato enable Auto-layout. - Set direction to Vertical.
- 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.
- Click the Horizontal padding field → select variable
- Turn the frame into a component (
Cmd + Option + K/Ctrl + Alt + K) and name itPrimitives/Stack. - Add a variant named
gapwith valuescompact,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.
- Create a frame, enable Auto-layout, and set direction to Horizontal.
- Set the wrap option to Wrap.
- Bind horizontal padding, vertical padding, and gap to
space/compact. - Turn into a component named
Primitives/Cluster. - 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.
- Create a frame, enable Auto-layout, set direction to Horizontal.
- Create inner column frames set to Fill container horizontally and Fill container vertically.
- Bind the layout gap to
space/default. - Create variants for standard column counts (
cols=2,cols=3,cols=4). - 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
- Build a primary button using an Auto-layout frame containing an icon slot and text layer.
- Bind fills, strokes, padding, and text colours directly to
Semanticcollection variables. - Create a component set named
Buttonwith 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".
- Variant property:
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.
- Select the component set on the canvas.
- 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).
- In the Description field, document the component details/constraints directly:
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.
- Create a generic placeholder component named
_Slot:- Draw a frame, press
Shift + Afor 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.
- Draw a frame, press
- Build the sub-components:
Card.Header************************: Auto-layout frame containing a title text layer and a nested instance ofButton. Select the button instance, click Create component property (target icon) next to the component name in the right panel, select Instance swap, name itactionSlot, and under Preferred values, click + to addButtonandIconButton.Card.Body************************: Auto-layout frame withspace/defaultpadding containing an instance of_Slot. Select_Slot, create an Instance swap property namedcontentSlot.Card.Footer************************: Auto-layout frame containing an instance ofPrimitives/Cluster. Select the cluster, create an Instance swap property namedfooterSlot.
- Assemble the parent
Cardcontainer:- Place instances of
Card.Header,Card.Body, andCard.Footerinside a parent Auto-layout frame. - Turn the frame into a component named
Card.
- Place instances of
- Surface nested controls via Nested instances:
- Select the parent
Cardcomponent. - In the Properties section of the right sidebar, click + → Nested instances.
- Check
Card.HeaderandCard.Footer. Sub-component properties will now appear directly on the top-levelCardproperties panel.
- Select the parent
- In the
Carddescription field, document the slot rules:
Slot Contract:
- actionSlot: Button (variant: 'ghost' | 'secondary' only)
- contentSlot: Stack, Text, FormField, or Table
- footerSlot: Cluster containing action buttonsBuild 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).
- Create the
TableCellcomponent:- Auto-layout frame with
space/compactpadding. - 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.
- Auto-layout frame with
- Create the
TableHeaderCellcomponent:- Same dimensions as
TableCell, usingtext/mutedand semi-bold typography.
- Same dimensions as
- Build the
TableRowcomponent:- Create an Auto-layout frame set to Horizontal, padding
0, gap0, 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.
- Create an Auto-layout frame set to Horizontal, padding
- Build the
TableHeaderRowcomponent usingTableHeaderCellinstances with matching column widths. - In the component description, document the composition rule:
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.
- Create the
FormFieldcomponent:- Frame set to vertical Auto-layout with
space/compactgap. - 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 namedinputSlot. Under Preferred values, addTextInput,Select, andTextarea. - Bottom layer: Text layer for validation text linked to a text property
helpText. Add a boolean propertyisError(switches text colour totext/critical).
- Frame set to vertical Auto-layout with
- Expose the nested input properties:
- Select the
FormFieldcomponent. - In the right sidebar, click + under Properties → Nested instances → select the
inputSlotinstance. - Selecting
FormFieldon canvas now exposes the input's placeholder and state directly on the primary panel.
- Select the
- In the component description, add:
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.
- 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.
- 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.
- Inspect auto-layout components in Dev Mode (
- 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.
- Export variables:
- Enterprise plans: Call the Figma Variables REST API (
GET /v1/files/:file_key/variables/local) using an access token withfile_variables:readpermissions. - 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.
- Enterprise plans: Call the Figma Variables REST API (
- Export component contracts:
- Retrieve component names, descriptions, and properties via the standard Figma Files REST API (
GET /v1/files/:file_key).
- Retrieve component names, descriptions, and properties via the standard Figma Files REST API (
- Assemble the manifest:
- Combine the exported tokens and component contracts into your project manifest file (e.g.
.cursor/rules/design-system.mdcorai-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).
- Combine the exported tokens and component contracts into your project manifest file (e.g.
Verify with an AI Code Agent
Goal: Validate that an unconstrained model generates production-ready UI when given the extracted manifest.
- Place the generated manifest file in the root or rules directory of a test project repository.
- In your agent of choice, run a scaffolding prompt:
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-systeminstead of writing custom markup? - Are layout wrappers constructed using
StackorClusterwith semantic spacing tokens? - Are colours referenced strictly by semantic aliases without arbitrary hex codes or custom utility classes?