Multibrand Design System for Figma
Creates a complete four-tier design system directly in the user's Figma file using the Figma MCP connection. No JSON file. No plugin. Everything appears in Figma's variable panel and Styles panel automatically.
Step 0 — Check the Figma connection
Before anything else, confirm you can write to the user's Figma file.
Preferred path — Figma Console MCP Desktop Bridge (works on any Figma plan,
and is the path this skill is written for). Check it with
figma_get_status({ probe: true }), then figma_list_open_files to confirm
which file is the active target. Every create/update step below runs through
figma_execute (raw Plugin API) or figma_batch_create_variables.
⚠ Under documentAccess: dynamic-page the synchronous Plugin API getters throw.
Always use the async forms inside figma_execute:
getLocalVariablesAsync, getLocalVariableCollectionsAsync,
getVariableByIdAsync, getLocalEffectStylesAsync, getLocalTextStylesAsync.
Fallback — official Figma MCP (https://mcp.figma.com/mcp) is read-oriented
(get_variable_defs, get_design_context) and cannot create variables. Use it
for inspection only; it is not sufficient for this skill.
If neither is connected, tell the user:
To create your design system directly in Figma, I need a write connection
to your Figma file. Here's how to set it up:
1. Install the Figma Console MCP server and open its Desktop Bridge plugin
inside the Figma file you want to build in
2. Confirm the bridge is live — the plugin window shows the connected
file name and port
3. Come back and run this skill again
The official Figma MCP (https://mcp.figma.com/mcp) can read a file but
cannot create variables, so it won't work for this on its own.
Alternative (no MCP): I can generate a JSON file instead that you import
via the "Variables Import/Export" Figma plugin
(https://www.figma.com/community/plugin/1253571037276775).
Just say "use JSON instead" and I'll switch to that approach.
Stop here until the user either connects MCP or asks for the JSON fallback. If they ask for JSON fallback, jump to → APPENDIX: JSON Fallback.
Step 1 — Gather brand configuration
Ask the user for all of the following in a single message:
To set up your design system, I need a few details:
1. Brand color (default: Emerald) — your primary brand color
2. Accent color (default: Violet) — secondary highlight color
3. Neutral color (default: Gray) — surfaces, borders, text
4. Success color (default: Green)
5. Warning color (default: Amber)
6. Error color (default: Red)
7. Info color (default: Blue)
Available Tailwind v4 hues:
Gray · Slate · Red · Rose · Orange · Amber · Yellow · Lime ·
Green · Emerald · Teal · Cyan · Sky · Blue · Indigo · Violet ·
Purple · Fuchsia · Pink
8. Body font family (default: Inter) — used for body text styles
9. Heading font (default: Roboto Mono) — used for h1–h6 styles
10. Project name (e.g. "acme-corp")
If the user has already specified any of these in the conversation, use those values and only ask for what's missing.
Step 2 — Show color preview and get approval
Once you have the config, show a visual approval card BEFORE creating anything.
Generate an inline color preview using HTML/SVG in your response. For each configured role show:
- A filled color swatch (use the scale/500 hex for the chosen hue)
- The role name (Brand, Accent, etc.)
- The Tailwind hue name
- The exact hex value at scale 500
Use the Tailwind v4 hex values from this reference table (scale 500):
Gray #71717a Slate #64748b Red #ef4444 Rose #f43f5e
Orange #f97316 Amber #f59e0b Yellow #eab308 Lime #84cc16
Green #22c55e Emerald #10b981 Teal #14b8a6 Cyan #06b6d4
Sky #0ea5e9 Blue #3b82f6 Indigo #6366f1 Violet #8b5cf6
Purple #a855f7 Fuchsia #d946ef Pink #ec4899
Then show the typography config:
- Body font: [font name] — used for body xs through body lg styles
- Heading font: [mono font name] — used for h1 through h6 styles
Then ask:
Does this look right? Reply "yes" to create the design system, or tell me
what to change (e.g. "change Brand to Blue" or "use DM Sans for headings").
Do not create anything in Figma until the user approves.
Step 3 — Create variables in Figma via MCP
Once approved, create all four variable collections through the Desktop Bridge. Tell the user: "Creating your design system in Figma now..."
Use figma_batch_create_variables for bulk creation, or figma_execute with
figma.variables.createVariableCollection() /
figma.variables.createVariable() when you need alias wiring in the same pass.
Create collections in tier order (Base → Typography → Alias → Map) so that
alias targets always exist before the variables that reference them.
Collection 1 — Base (mode: Value)
Colors — grouped under Colours/, all 19 Tailwind v4 hues, each with
11 scales (50→950): Colours/Gray/50 … Colours/Pink/950 for
Gray, Slate, Red, Rose, Orange, Amber, Yellow, Lime, Green, Emerald,
Teal, Cyan, Sky, Blue, Indigo, Violet, Purple, Fuchsia, Pink
(Use the full hex table in references/architecture.md)
Note: There is NO alpha ramp in Base. Translucent alpha washes live in
the Alias collection (Alpha/light mode/* and Alpha/dark mode/* — see
Collection 3), because they are DERIVED from the user's chosen Neutral hue
(50 and 950 endpoints) and need separate light and dark variants.
Spacing — FLOAT variables, a compact Tailwind-style spacing scale
(replaces the old dense size/* linear scale). Includes two negative
values for inset/overlap use cases:
Spacing/-4 Spacing/-2 Spacing/0 Spacing/1 Spacing/2
Spacing/4 Spacing/6 Spacing/8 Spacing/12 Spacing/16
Spacing/20 Spacing/24 Spacing/32 Spacing/40 Spacing/48
Spacing/56 Spacing/64 Spacing/80 Spacing/120
Radius — FLOAT variables: radius/none(0) · radius/xs(2) · radius/sm(4) · radius/md(6) · radius/lg(8) · radius/xl(12) · radius/2xl(16) · radius/3xl(24) · radius/full(9999)
Collection 2 — Typography (mode: Value)
family/Body STRING → user's body font (e.g. "Inter")
family/Title STRING → user's heading font (e.g. "Roboto Mono")
weight/regular FLOAT → 400
weight/medium FLOAT → 500
weight/semibold FLOAT → 600
weight/bold FLOAT → 700
size/body xs FLOAT → 10
size/body sm FLOAT → 12
size/body md FLOAT → 14
size/body rg FLOAT → 16
size/body lg FLOAT → 18
size/h6 FLOAT → 20
size/h5 FLOAT → 24
size/h4 FLOAT → 32
size/h3 FLOAT → 40
size/h2 FLOAT → 56
size/h1 FLOAT → 64
lh/body xs FLOAT → 16
lh/body sm FLOAT → 18
lh/body md FLOAT → 22
lh/body rg FLOAT → 24
lh/body lg FLOAT → 28
lh/h6 FLOAT → 32
lh/h5 FLOAT → 28
lh/h4 FLOAT → 38
lh/h3 FLOAT → 44
lh/h2 FLOAT → 64
lh/h1 FLOAT → 72
ls/tight FLOAT → -0.5
ls/normal FLOAT → 0
ls/relaxed FLOAT → 0.5
ls/loose FLOAT → 1
style/normal STRING → "normal"
style/italic STRING → "italic"
Collection 3 — Alias (mode: Theme 1 (Default))
Each role maps the full 50→950 scale to the user's chosen Base hue. Every value is an alias reference to Base, e.g.: Brand/500 → alias to Base / [chosen hue] / 500
Roles: Brand · Accent · Neutral · Success · Warning · Error · Info
Chart series (alias to specific Base scale steps):
Chart/series-1/light mode → Base / Blue / 500
Chart/series-1/dark mode → Base / Blue / 400
Chart/series-2/light mode → Base / Amber / 400
Chart/series-2/dark mode → Base / Amber / 300
Chart/series-3/light mode → Base / Rose / 600
Chart/series-3/dark mode → Base / Rose / 400
Chart/series-4/light mode → Base / Teal / 400
Chart/series-4/dark mode → Base / Teal / 300
Chart/series-5/light mode → Base / Violet / 700
Chart/series-5/dark mode → Base / Violet / 400
Alpha washes — derived from the user's Neutral role, not hardcoded.
The derivation rule:
Alpha/light mode/*takes the RGB of the user's chosen Neutral hue at scale 50 (i.e. whateverNeutral/50resolves to) and lowers only the opacity per step: 10%, 20%, 40%, 60%, 80%.Alpha/dark mode/*takes the RGB of the chosen Neutral hue at scale 950 (whateverNeutral/950resolves to) and applies the same opacity steps.
So if the user picks Gray as Neutral the washes are built on #f9fafb /
#030712; if they pick Slate they're built on #f8fafc / #020617 —
always recompute from the configured Neutral hue's 50 and 950 hex values.
These are stored as raw COLOR values with translucency (NOT aliases) — Figma variables cannot apply an opacity modifier on top of an aliased color, so the RGB is baked in at creation time:
Alpha/light mode/100 → rgba([Neutral hue]/50, 10%)
Alpha/light mode/200 → rgba([Neutral hue]/50, 20%)
Alpha/light mode/400 → rgba([Neutral hue]/50, 40%)
Alpha/light mode/600 → rgba([Neutral hue]/50, 60%)
Alpha/light mode/800 → rgba([Neutral hue]/50, 80%)
Alpha/dark mode/100 → rgba([Neutral hue]/950, 10%)
Alpha/dark mode/200 → rgba([Neutral hue]/950, 20%)
Alpha/dark mode/400 → rgba([Neutral hue]/950, 40%)
Alpha/dark mode/600 → rgba([Neutral hue]/950, 60%)
Alpha/dark mode/800 → rgba([Neutral hue]/950, 80%)
Drop shadow tokens — TWO directional groups, Down (↓, positive Y) and
Up (↑, negative Y). X/Y/Blur/Spread are raw FLOATs; Color aliases to the
Alias Alpha/dark mode washes (shadows are always dark, in both themes).
sm/md/lg use the 20% wash; xl uses the deeper 40% wash. This is what
Step 5 binds the effect styles to:
Drop shadow/Down/sm ↓/X FLOAT → 0
Drop shadow/Down/sm ↓/Y FLOAT → 8
Drop shadow/Down/sm ↓/Blur FLOAT → 8
Drop shadow/Down/sm ↓/Spread FLOAT → -6
Drop shadow/Down/sm ↓/Color COLOR → alias to Alias / Alpha/dark mode/200
Drop shadow/Down/md ↓/X FLOAT → 0
Drop shadow/Down/md ↓/Y FLOAT → 8
Drop shadow/Down/md ↓/Blur FLOAT → 16
Drop shadow/Down/md ↓/Spread FLOAT → -6
Drop shadow/Down/md ↓/Color COLOR → alias to Alias / Alpha/dark mode/200
Drop shadow/Down/lg ↓/X FLOAT → 0
Drop shadow/Down/lg ↓/Y FLOAT → 8
Drop shadow/Down/lg ↓/Blur FLOAT → 32
Drop shadow/Down/lg ↓/Spread FLOAT → -6
Drop shadow/Down/lg ↓/Color COLOR → alias to Alias / Alpha/dark mode/200
Drop shadow/Down/xl ↓/X FLOAT → 0
Drop shadow/Down/xl ↓/Y FLOAT → 8
Drop shadow/Down/xl ↓/Blur FLOAT → 48
Drop shadow/Down/xl ↓/Spread FLOAT → -12
Drop shadow/Down/xl ↓/Color COLOR → alias to Alias / Alpha/dark mode/400
Drop shadow/Up/{size} ↑/* — identical to the Down group, except
Y → -8 for every size. Same Blur/Spread/
Color per size (xl color → dark mode/400).
Background blur tokens (raw FLOATs — bound to the BACKGROUND_BLUR effect styles in Step 5):
Background blur/sm FLOAT → 5
Background blur/md FLOAT → 10
Background blur/lg FLOAT → 30
Background blur/xl FLOAT → 100
Collection 4 — Map (modes: Light · Dark)
All tokens alias to Alias collection. Full slot tables in references/architecture.md. Summary:
Background — Neutral (13 slots incl. quartiary), brand-like (11 slots)
⚠ Light-mode primary / primary hover / pressed are set PER HUE from
measured contrast, not one global step (see Scale conventions below):
Brand 700 → 800 → 900 (pale ink)
Error 700 → 800 → 900 (pale ink)
Accent 600 → 700 → 800 (pale ink)
Info 600 → 700 → 800 (pale ink)
Success 500 → 400 → 300 (dark ink)
Warning 500 → 400 → 300 (dark ink)
Dark-mode for every brand-like role: 400 → 300 → 200 (dark ink).
Brand-like tertiary = deep surface (900 L / 100 D); inverse = palest
tint (50 L / 950 D).
Text — Neutral + brand-like (8 slots each)
Brand-like primary = 700 L / 400 D (700 is the lightest step that clears
4.5:1 against the neutral surface for all six roles).
Brand-like tertiary = 950 L / 50 D (max-contrast tinted ink);
inverse = 50 L / 950 D.
Border — Neutral + brand-like (7 slots each)
Icon — mirrors Text exactly
Chart — 5 series, each with Light → Alias/Chart/series-N/light mode
Dark → Alias/Chart/series-N/dark mode
Overlay — 5 scrim steps aliasing the mode-appropriate Alpha wash:
Overlay/sm Light → Alias/Alpha/light mode/100 Dark → Alias/Alpha/dark mode/100
Overlay/md Light → Alias/Alpha/light mode/200 Dark → Alias/Alpha/dark mode/200
Overlay/lg Light → Alias/Alpha/light mode/400 Dark → Alias/Alpha/dark mode/400
Overlay/xl Light → Alias/Alpha/light mode/600 Dark → Alias/Alpha/dark mode/600
Overlay/xxl Light → Alias/Alpha/light mode/800 Dark → Alias/Alpha/dark mode/800
Step 4 — Create text styles in Figma via MCP
After all variables are created, create the 25 text styles via figma_execute
using figma.createTextStyle(). Load each font with
await figma.loadFontAsync({ family, style }) before assigning it, then bind
each property with style.setBoundVariable(field, variable).
Critical: Every property must be bound to the Typography variable we just created — including fontWeight. Do NOT hardcode any values.
Variable binding for each property:
fontFamily → 2. Typography / family/Body (body styles)
→ 2. Typography / family/Title (heading styles)
fontSize → 2. Typography / size/[token]
fontWeight → 2. Typography / weight/[token] ← MUST be a variable ref
lineHeight → 2. Typography / lh/[token]
letterSpacing → 2. Typography / ls/[token]
The 25 text styles:
Body styles (fontFamily → family/Body, letterSpacing → ls/relaxed):
Name size token weight token lh token
body xs size/body xs weight/regular lh/body xs
body xs-M size/body xs weight/medium lh/body xs
body sm size/body sm weight/regular lh/body sm
body sm-M size/body sm weight/medium lh/body sm
body md size/body md weight/regular lh/body md
body md-M size/body md weight/medium lh/body md
body md-SB size/body md weight/semibold lh/body md
body rg size/body rg weight/regular lh/body rg
body rg-M size/body rg weight/medium lh/body rg
body rg-SB size/body rg weight/semibold lh/body rg
body lg size/body lg weight/regular lh/body lg
body lg-M size/body lg weight/medium lh/body lg
body lg-SB size/body lg weight/semibold lh/body lg
Heading styles (fontFamily → family/Title):
Name size token weight token lh token ls token
h6 size/h6 weight/medium lh/h6 ls/normal
h6-SB size/h6 weight/semibold lh/h6 ls/normal
h5 size/h5 weight/medium lh/h5 ls/normal
h5-SB size/h5 weight/semibold lh/h5 ls/normal
h4 size/h4 weight/medium lh/h4 ls/tight
h4-SB size/h4 weight/semibold lh/h4 ls/tight
h3 size/h3 weight/medium lh/h3 ls/tight
h3-B size/h3 weight/bold lh/h3 ls/tight
h2 size/h2 weight/medium lh/h2 ls/tight
h2-B size/h2 weight/bold lh/h2 ls/tight
h1 size/h1 weight/medium lh/h1 ls/tight
h1-B size/h1 weight/bold lh/h1 ls/tight
Step 5 — Create effect styles via MCP (12 total)
After the Alias Drop shadow and Background blur variables exist,
create 12 EFFECT styles. Every numeric/color property must be bound to a
variable — never hardcoded.
8 drop shadow styles (DROP_SHADOW type)
Named with a direction group and arrow suffix:
Drop shadow/sm ↓ Drop shadow/md ↓ Drop shadow/lg ↓ Drop shadow/xl ↓
Drop shadow/sm ↑ Drop shadow/md ↑ Drop shadow/lg ↑ Drop shadow/xl ↑
Binding pattern — every field binds to the matching Alias-tier token
for that direction and size (the color binds THROUGH the Alias
Drop shadow/.../Color token, which itself aliases Alpha/dark mode/200
or /400 — do NOT bind color straight to a raw alpha variable):
color → Alias / Drop shadow/{Down|Up}/{size} {↓|↑}/Color
offsetX → Alias / Drop shadow/{Down|Up}/{size} {↓|↑}/X
offsetY → Alias / Drop shadow/{Down|Up}/{size} {↓|↑}/Y
radius → Alias / Drop shadow/{Down|Up}/{size} {↓|↑}/Blur (Figma's
effect field name for blur is "radius")
spread → Alias / Drop shadow/{Down|Up}/{size} {↓|↑}/Spread
⚠ Down styles must bind ONLY to Drop shadow/Down/... tokens and Up
styles ONLY to Drop shadow/Up/... tokens — never mix directions across
the five fields of one style.
Use the Plugin API's figma.variables.setBoundVariableForEffect(effect, field, variable)
— it returns a new effect object per call, so reassign after each bind:
let effect = {
type: 'DROP_SHADOW',
color: { r, g, b, a }, // seed values only; overwritten by the bound variable
offset: { x: 0, y: 8 }, // y: -8 for Up styles
radius: 8,
spread: -6,
visible: true,
blendMode: 'NORMAL'
};
effect = figma.variables.setBoundVariableForEffect(effect, 'color', colorVar);
effect = figma.variables.setBoundVariableForEffect(effect, 'offsetX', xVar);
effect = figma.variables.setBoundVariableForEffect(effect, 'offsetY', yVar);
effect = figma.variables.setBoundVariableForEffect(effect, 'radius', blurVar);
effect = figma.variables.setBoundVariableForEffect(effect, 'spread', spreadVar);
const style = figma.createEffectStyle();
style.name = 'Drop shadow/sm ↓'; // etc.
style.effects = [effect];
Seed values per size (used only as the initial unbound value — the bound variable drives the real value). offset.x is 0 for all sizes; offset.y is +8 for ↓ styles and -8 for ↑ styles:
| Style | radius (blur) | spread | color alpha |
|---|---|---|---|
| sm | 8 | -6 | 20% |
| md | 16 | -6 | 20% |
| lg | 32 | -6 | 20% |
| xl | 48 | -12 | 40% |
4 background blur styles (BACKGROUND_BLUR type)
Named Background blur/sm, Background blur/md, Background blur/lg,
Background blur/xl. Single effect each; the radius binds to the Alias
Background blur/{size} token:
let effect = {
type: 'BACKGROUND_BLUR',
radius: 5, // seed only — 5/10/30/100 per size
visible: true
};
effect = figma.variables.setBoundVariableForEffect(effect, 'radius', blurVar);
const style = figma.createEffectStyle();
style.name = 'Background blur/sm'; // etc.
style.effects = [effect];
Step 6 — Confirm completion
Once everything is created, tell the user:
✅ Your design system is ready in Figma!
Created:
• 4 variable collections (~662 variables)
- 1. Base — Colours/ (19 hues × 11 scales) + Spacing + radius
- 2. Typography — font, weight, size, line-height, spacing, style
- 3. Alias — semantic roles (Brand → [hue], Accent → [hue], etc.)
+ Alpha washes (light/dark) + Drop shadow (Down ↓ / Up ↑)
+ Background blur tokens
- 4. Map — light/dark UI tokens for Background, Text, Border, Icon,
Chart, Overlay
• 25 text styles — body xs through h1-B, all properties bound to variables
• 12 effect styles — 8 drop shadows (sm–xl, ↓ and ↑) + 4 background blurs,
all properties bound to variables
Open Figma's Variables panel (right-click canvas → Edit Variables) to see
all four collections. Text styles and effect styles appear in the Styles panel.
What the system contains
Collections and modes
| Collection | Mode(s) | Purpose |
|---|---|---|
| 1. Base | Value (1 mode) | Raw Tailwind v4 primitives |
| 2. Typography | Value (1 mode) | Raw text primitives |
| 3. Alias | Theme 1 (Default) | Semantic role → Base hue mapping |
| 4. Map | Light + Dark | UI token → Alias resolution |
Scale conventions (contrast-measured against WCAG AA 4.5:1)
Every hue ramp has a dead zone — the step where the pale ink and the dark ink both fall below 4.5:1. It sits at the ink crossover and moves per hue:
| Role | Hue | Dead zone |
|---|---|---|
| Brand | Emerald | 600 |
| Accent | Violet | 500 |
| Error | Red | 500–600 |
| Info | Blue | 500 |
| Success | Green | none |
| Warning | Amber | none |
Success and Warning have no dead zone: they are high-luminance hues, so the crossover lands where both inks still clear 4.5:1. That is why they can sit at 500 with dark ink and still measure best in the set.
Because the dead zone moves, light-mode filled surfaces are set per hue rather than on one global step:
- Background brand-like primary (light): Brand 700 · Error 700 · Accent 600 · Info 600 with pale ink; Success 500 · Warning 500 with dark ink
- Background brand-like hover/pressed (light): pale-ink roles step one and two darker; dark-ink roles step lighter (400 / 300) to keep dark ink legible
- Background brand-like (dark): 400 → hover 300 → pressed 200, dark ink (hover must NOT be 500 — that is inside the Accent and Info dead zone)
- Text/Icon neutral primary: 950 light / 50 dark
- Text/Icon brand-like primary: 700 light / 400 dark — uniform, since 700 is the lightest step clearing 4.5:1 on the neutral surface for all six roles
- Text/Icon brand-like tertiary: 950 light / 50 dark; inverse: 50 light / 950 dark
- Chart colors: colorblind-safe (deuteranopia validated)
Measured worst cases across 36 surface states + 12 colored-text pairings: Info light primary 4.75:1, Success light text 4.80:1. Zero failures.
⚠ Filled surfaces do not all take the same ink token. Pale-ink roles
(Brand/Accent/Error/Info) pair with Text/{role}/inverse; dark-ink roles
(Success/Warning) pair with Text/{role}/tertiary.
Effect styles
Drop shadows come in two directions: ↓ (default, elevation below) and ↑ (shadow cast upward — bottom sheets, docked toolbars, footers).
| Size | Purpose | Blur | Spread | Color alpha |
|---|---|---|---|---|
| sm | subtle elevation (cards, chips) | 8 | -6 | 20% |
| md | default elevation (dropdowns, popovers) | 16 | -6 | 20% |
| lg | high elevation (modals) | 32 | -6 | 20% |
| xl | maximum elevation (dialogs, spotlights) | 48 | -12 | 40% |
Background blurs (glassmorphism / frosted panels):
| Style | Radius |
|---|---|
| Background blur/sm | 5 |
| Background blur/md | 10 |
| Background blur/lg | 30 |
| Background blur/xl | 100 |
For full slot-level details → references/architecture.md
APPENDIX: JSON Fallback
If the user doesn't have Figma MCP, fall back to the generator script:
node [skill-dir]/scripts/generate.js \
--brand Emerald --accent Violet --neutral Gray \
--success Green --warning Amber --error Red --info Blue \
--font "Inter" --mono "Roboto Mono" \
--name "project-name" --out ./
Then import with the Variables Import/Export plugin: https://www.figma.com/community/plugin/1253571037276775
Note on font weight in JSON fallback: Figma's REST API does not support binding fontWeight to a variable reference in text styles. The weight is stored as a variable ref in the JSON, and the plugin will resolve it to the numeric value (400/500/600/700) on import. The variable itself (weight/regular etc.) exists in the Typography collection and can be used for variable bindings in components — it just can't drive text style fontWeight directly.