Design System Prompt Library: Getting Consistent UI from AI
What is a design system prompt library?
A design system prompt library is a structured collection of machine-readable specifications — design tokens in JSON, global consistency rules, and per-component prompts — that provides an LLM with an unambiguous visual contract so every generated UI component uses only approved colors, spacing, typography, and interaction patterns, regardless of session or context.
TL;DR
- -Most LLM-generated UI fails visual QA on the first attempt; the root cause is not model quality but the absence of formalized, unambiguous rules — which a prompt library provides.
- -Each component prompt has five mandatory sections: identity, visual specification (token values only), behavior rules, layout constraints, and code output format — drop any section and the model has room to improvise.
- -Tokens must be included explicitly in every prompt — the model has no memory between requests and will fall back to defaults without them.
- -Programmatic assembly (tokens + consistency rules + component spec) keeps the library maintainable: update one token file and all prompts reflect the change automatically.
- -In a modeled example, introducing the library roughly doubles or triples first-pass acceptance rate (e.g., 27% to 81%) and cuts regeneration iterations by about half (3.2 to 1.4 per component).
LLM-generated UI fails visual QA on the first attempt more often than not. The root cause isn’t model quality — it’s the absence of formalized rules the model can interpret without ambiguity.
A design system in Figma solves the consistency problem for humans. A prompt library solves it for AI. This article covers how to structure that library: from design tokens to full component prompts with validation.
Design Tokens as the Foundation of Prompts
Design tokens in the context of AI generation serve two functions. First: a single source of truth for visual parameters. Second: a machine-readable contract that kills “eyeballed” interpretation.
Standard JSON format for design tokens — the Design Tokens Format Module from the DTCG (Design Tokens Community Group at W3C):
{
"color": {
"primary": { "$value": "#1a1a2e", "$type": "color" },
"accent": { "$value": "#e2a832", "$type": "color" },
"surface": { "$value": "#16213e", "$type": "color" },
"text-primary": { "$value": "#eaeaea", "$type": "color" },
"text-secondary": { "$value": "#a0a0b0", "$type": "color" },
"error": { "$value": "#e74c3c", "$type": "color" },
"success": { "$value": "#2ecc71", "$type": "color" }
},
"spacing": {
"xs": { "$value": "4px", "$type": "dimension" },
"sm": { "$value": "8px", "$type": "dimension" },
"md": { "$value": "16px", "$type": "dimension" },
"lg": { "$value": "24px", "$type": "dimension" },
"xl": { "$value": "32px", "$type": "dimension" },
"2xl": { "$value": "48px", "$type": "dimension" }
},
"typography": {
"heading-1": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "32px", "$type": "dimension" },
"fontWeight": { "$value": 700, "$type": "number" },
"lineHeight": { "$value": 1.2, "$type": "number" }
},
"body": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "16px", "$type": "dimension" },
"fontWeight": { "$value": 400, "$type": "number" },
"lineHeight": { "$value": 1.5, "$type": "number" }
}
},
"radius": {
"sm": { "$value": "4px", "$type": "dimension" },
"md": { "$value": "8px", "$type": "dimension" },
"lg": { "$value": "16px", "$type": "dimension" },
"full": { "$value": "9999px", "$type": "dimension" }
}
}
The accent color (#e2a832) in this example isn’t arbitrary — it’s the real accent color from this blog’s own design system, futurecraft.pro. The other tokens in the example are illustrative, used to demonstrate the structure.
Tokens go into a prompt in full or in fragments, depending on the task. The full set is needed for generating a page. The color + typography subset is enough for a text component.
One thing that trips people up: the model doesn’t “remember” tokens between requests. Every prompt has to include all necessary tokens explicitly. Skip this and the model falls back on defaults, breaking consistency. For more on context management, see the context engineering guide.
Component Prompt Structure
A component prompt describes one UI element: a button, a card, navigation, a form. It has five sections.
Section 1: Identity
Component name, its role in the interface, variants.
Component: Button
Role: Primary user action trigger
Variants: primary, secondary, ghost, danger
States: default, hover, active, disabled, loading
Section 2: Visual specification
Exact values from design tokens. No vague descriptions like “large font” or “bright color.”
[variant: primary]
background: {color.accent}
text-color: {color.primary}
font: {typography.body}, fontWeight: 600
padding: {spacing.sm} {spacing.lg}
border-radius: {radius.md}
min-height: 44px
[variant: secondary]
background: transparent
border: 1px solid {color.accent}
text-color: {color.accent}
font: {typography.body}, fontWeight: 600
padding: {spacing.sm} {spacing.lg}
border-radius: {radius.md}
Section 3: Behavior rules
Interactive states and transitions. The model needs to know what happens on hover, click, and focus — don’t leave it to guess.
Hover: background opacity 0.9, cursor: pointer
Active: scale(0.98), transition 100ms
Disabled: opacity 0.5, pointer-events: none
Loading: replace text with spinner (16x16), maintain button dimensions
Focus: outline 2px solid {color.accent}, offset 2px
Section 4: Layout constraints
Minimum and maximum sizes, placement rules, spacing from neighboring elements.
Min-width: 120px
Max-width: 100% of parent container
Margin between adjacent buttons: {spacing.sm}
Icon + text: icon 20x20, gap {spacing.xs}, icon left of text
Full-width on viewport < 640px
Section 5: Code output format
The expected output format. Without this section, the model picks whatever format it feels like.
Output: React component with TypeScript
Styling: Tailwind CSS classes only
Props interface: { variant, size, disabled, loading, onClick, children }
No inline styles. No CSS modules. No styled-components.
Export: named export, not default
Five sections cover the full lifecycle of a component. Drop any one and you’re giving the model room for “creativity” — which, in a design system, is just a polite word for breakage.
Consistency Rules: Global Constraints
Component prompts describe individual elements. Consistency rules describe how those elements relate to each other — constraints that apply across the whole interface.
Typography rules
TYPOGRAPHY RULES:
- Heading hierarchy: h1 > h2 > h3. Never skip levels.
- Maximum 2 font families per page: {typography.heading.fontFamily} for headings,
{typography.body.fontFamily} for body text.
- Line length: 45-75 characters for body text. Enforce max-width on text containers.
- No font sizes outside the token scale. If a size is not in tokens, it does not exist.
Spacing rules
SPACING RULES:
- Vertical rhythm: all vertical spacing is a multiple of {spacing.sm} (8px).
- Section spacing: {spacing.2xl} between major sections.
- Component internal spacing: {spacing.md} as default padding.
- Related elements: {spacing.sm} gap. Unrelated elements: {spacing.lg} minimum.
- Never use arbitrary values (5px, 13px, 22px). Only token values.
Color rules
COLOR RULES:
- Background/text contrast ratio: minimum 4.5:1 (WCAG AA).
- Accent color ({color.accent}) used for: CTAs, active states, links. Not for backgrounds.
- Maximum 1 accent color per viewport. No competing visual anchors.
- Error states: {color.error} for borders and icons, not for text.
- Dark surfaces: {color.primary} or {color.surface}. No other dark values.
Icon and media rules
ICON RULES:
- Icon sizes: 16px (inline), 20px (button), 24px (standalone), 32px (feature).
- Stroke width: 1.5px for all icons. Consistent across the system.
- Icon color inherits text color of parent. No hardcoded icon colors.
- Decorative icons: aria-hidden="true". Functional icons: aria-label required.
IMAGE RULES:
- Aspect ratios: 16:9 (hero), 4:3 (card), 1:1 (avatar).
- Always include width, height, alt attributes.
- Lazy loading for images below the fold.
Consistency rules go into every prompt as a preamble. A component prompt without them produces a technically correct component that still looks out of place.
Composition Prompt: Pages and Sections
Individual components get assembled into sections, sections into pages. A composition prompt describes that higher-level structure.
PAGE: Pricing Page
LAYOUT: single column, max-width 1200px, centered
SECTIONS (top to bottom):
1. Hero: heading (h1) + subheading (body) + CTA (Button:primary)
- Heading: max 8 words
- Subheading: max 2 lines
- Vertical spacing: {spacing.xl} between elements
2. Pricing Cards: 3-column grid, gap {spacing.lg}
- Each card: Card component
- Highlighted card: border 2px solid {color.accent}
- Cards equal height (flexbox align-stretch)
3. FAQ: Accordion component, max 6 items
- Section heading: h2
- Spacing from pricing cards: {spacing.2xl}
4. CTA Footer: centered text + Button:primary
- Background: {color.surface}
- Padding: {spacing.2xl} vertical
RESPONSIVE:
- < 1024px: pricing cards → 2 columns
- < 640px: pricing cards → 1 column, full-width buttons
A composition prompt references component prompts by name (Card component, Button:primary, Accordion component). Component definitions come in separately or within the same context window.
Organizing the Prompt Library
A prompt library needs a file structure. Dump everything into one document and it becomes unmanageable after 10–15 components.
prompt-library/
├── tokens/
│ ├── colors.json
│ ├── typography.json
│ ├── spacing.json
│ └── radius.json
├── rules/
│ ├── consistency.md
│ ├── accessibility.md
│ └── responsive.md
├── components/
│ ├── button.md
│ ├── card.md
│ ├── input.md
│ ├── modal.md
│ ├── navigation.md
│ ├── table.md
│ └── accordion.md
├── compositions/
│ ├── pricing-page.md
│ ├── dashboard-layout.md
│ ├── auth-flow.md
│ └── settings-page.md
└── templates/
├── component-template.md
└── composition-template.md
Each file is self-contained but references tokens and rules. The final prompt gets assembled programmatically: a script combines consistency.md + the relevant tokens + a specific component prompt into a single API request.
Example assembly in Python:
def build_component_prompt(component_name: str, tokens: list[str]) -> str:
rules = read_file("rules/consistency.md")
a11y = read_file("rules/accessibility.md")
token_data = {}
for token_file in tokens:
token_data.update(load_json(f"tokens/{token_file}.json"))
component = read_file(f"components/{component_name}.md")
return f"""You are a UI component generator.
## Design Tokens
{json.dumps(token_data, indent=2)}
## Consistency Rules
{rules}
## Accessibility Rules
{a11y}
## Component Specification
{component}
Generate the component following ALL rules above. Output ONLY code, no explanations."""
# Usage
prompt = build_component_prompt("button", ["colors", "typography", "spacing", "radius"])
Programmatic assembly gives you three things. Tokens update in one place and flow into all prompts automatically. Rules don’t get duplicated. And each prompt is scoped to the task — you’re not burning context window on components that have nothing to do with what you’re building.
Validating Results: Automated Checks
Generation without validation is useless. A set of automated checks catches the typical model errors.
Token validation
ALLOWED_COLORS = {"#1a1a2e", "#e2a832", "#16213e", "#eaeaea", "#a0a0b0", "#e74c3c", "#2ecc71"}
ALLOWED_SPACING = {"4px", "8px", "16px", "24px", "32px", "48px"}
def validate_tokens(generated_code: str) -> list[str]:
violations = []
hex_colors = re.findall(r'#[0-9a-fA-F]{6}', generated_code)
for color in hex_colors:
if color.lower() not in ALLOWED_COLORS:
violations.append(f"Unauthorized color: {color}")
px_values = re.findall(r'(\d+)px', generated_code)
for val in px_values:
px = f"{val}px"
if px not in ALLOWED_SPACING and int(val) not in [0, 1, 2, 16, 20, 24, 32, 44]:
violations.append(f"Non-standard spacing: {px}")
return violations
Accessibility validation
def validate_a11y(generated_code: str) -> list[str]:
violations = []
img_tags = re.findall(r'<img[^>]*>', generated_code)
for img in img_tags:
if 'alt=' not in img:
violations.append(f"Image missing alt attribute: {img[:50]}")
button_tags = re.findall(r'<button[^>]*>.*?</button>', generated_code, re.DOTALL)
for btn in button_tags:
if 'aria-label' not in btn and '>' not in btn.split('</')[0].split('>')[1]:
violations.append("Button without accessible label")
return violations
Structure validation
def validate_heading_hierarchy(generated_code: str) -> list[str]:
violations = []
headings = re.findall(r'<h(\d)', generated_code)
levels = [int(h) for h in headings]
for i in range(1, len(levels)):
if levels[i] > levels[i-1] + 1:
violations.append(
f"Heading hierarchy skip: h{levels[i-1]} → h{levels[i]}"
)
return violations
All three checks run in a post-processing pipeline. If violations exceed a threshold, the result gets rejected and the prompt goes back with: “Previous output contained these violations: [list]. Fix them.”
Prompt Templates for Common Tasks
The library includes ready-made templates for common scenarios. A template formalizes a task so the model produces a predictable result rather than improvising.
Template: new component
TASK: Create a new UI component
COMPONENT: [name]
DESIGN TOKENS: [attached]
CONSISTENCY RULES: [attached]
REQUIREMENTS:
- Follow the 5-section component spec format (identity, visual, behavior, layout, output)
- Use ONLY provided design tokens for all visual values
- Include all interactive states
- TypeScript + Tailwind CSS
- WCAG AA compliance
OUTPUT:
1. Component code (.tsx)
2. Props interface
3. Usage example (3 variants)
Template: adapting an existing component
TASK: Adapt existing component to design system
CURRENT CODE: [attached]
DESIGN TOKENS: [attached]
CONSISTENCY RULES: [attached]
INSTRUCTIONS:
- Replace all hardcoded color values with design token equivalents
- Replace all hardcoded spacing with token values
- Preserve existing functionality and props API
- Add missing interactive states (hover, focus, disabled)
- Fix accessibility violations
OUTPUT:
1. Updated component code
2. List of changes made (before → after)
Template: page audit
TASK: Audit page against design system
PAGE CODE: [attached]
DESIGN TOKENS: [attached]
CONSISTENCY RULES: [attached]
CHECK:
- Token compliance (unauthorized colors, spacing, fonts)
- Heading hierarchy
- Contrast ratios
- Responsive breakpoints
- Component consistency (same component, same styling everywhere)
OUTPUT FORMAT:
| Issue | Location | Severity | Fix |
|-------|----------|----------|-----|
Versioning and Evolution
The prompt library lives in git. Change the color.accent token from #e2a832 to #f0b429, commit it, and every prompt that references that token picks up the new value automatically through the JSON file.
Semantic versioning for prompts:
- Patch (1.0.x): wording fixes without behavior changes
- Minor (1.x.0): new component or new variant of an existing one
- Major (x.0.0): token changes, breaking change in consistency rules
Each version gets tested: run 10–15 reference generations through validation. If the violation rate went up after a prompt change, roll it back.
Effectiveness Metrics
Four metrics tell you whether the library’s working.
Token compliance rate. Percentage of generated values that match design tokens. Target: 95%+.
First-pass acceptance. Percentage of results that pass validation on the first attempt. Modeled example: around 27% before the library, around 81% after — an order-of-magnitude estimate, not a measured benchmark.
Regeneration rate. How many times a component needs to be regenerated before it’s acceptable. Modeled example: with the library, the average drops from 3.2 to 1.4 iterations.
Cross-component consistency. Visual uniformity between components generated in different sessions. Measured automatically by comparing extracted CSS properties against a reference set.
Limitations and Trade-offs
The prompt library doesn’t solve every UI generation problem.
Context windows are limited. A full set of tokens + rules + component prompt + composition takes 3,000–5,000 tokens. For a complex page with 8–10 components, that’s 15,000–20,000 tokens just for instructions. The fix: hierarchical generation. First the layout and sections, then each component in its own request.
Models handle Tailwind classes better than raw CSS. If your design system uses CSS-in-JS or CSS Modules, you’ll need additional examples for each pattern.
Animations and microinteractions are harder to describe in prompts than static components. For complex transitions, reference code beats a text description every time.
Minimum Viable Prompt Library
Five artifacts, two to three hours:
- Design tokens in JSON — colors, spacing, typography. Three files, 50–100 lines.
- Consistency rules — typography, color, spacing in one document. 30–40 lines.
- Three component prompts — Button, Card, Input cover 60% of a typical interface.
- Assembly script — 20 lines combining tokens + rules + component into one prompt.
- Basic validation — hex colors and spacing checked against tokens. 15 lines.
After that, every component gets generated to a standard. The library grows by adding one file per component to components/ — nothing else needs to change.
Need help building an AI-powered design system workflow? I help startups build AI products and automate processes — belov.works.
FAQ
How do you handle component variants that require tokens not yet defined in the design system?
The correct approach is to block generation and add the token first, rather than letting the model invent an ad-hoc value. When a prompt returns a component with a hardcoded value that doesn’t match any existing token, treat it as a signal that the token library has a gap — not as an exception to allow. Add the missing token to the appropriate JSON file (e.g., a color.warning or spacing.compact value), commit it, then re-run the component prompt. This keeps the library as the single source of truth and prevents token debt that grows into a refactoring problem.
What’s the most reliable way to specify interactive states like hover and focus when the model tends to omit them?
The most effective technique is to enumerate states explicitly in the component identity section rather than relying on implicit instructions like “include all interactive states.” Specify each state by name with its expected visual delta: focus: outline 2px solid {color.accent}, outline-offset 2px. Models generate more complete and consistent state handling when the prompt lists states as discrete deliverables rather than as a general requirement. For Tailwind-based components, also specify the modifier class format — hover:bg-[{color.primary.hover}] — otherwise the model may use arbitrary values for hover states.
How do you maintain prompt library consistency when multiple team members are contributing component specs?
Store the component template in templates/component-template.md and enforce its use via a PR review checklist that verifies all five sections are present (identity, visual, behavior, layout, output). Run the validation scripts on generated output as part of CI — failed token compliance or accessibility checks block merges. The deeper consistency problem is semantic: two people may spec “error state” differently for buttons vs. inputs. A shared glossary document in rules/ that defines terms like “error,” “disabled,” and “loading” prevents divergence in how different components describe the same concept.