Patterns & Practices
Advanced techniques for working with specs — progressive precision, behavioral specification, integrated specs, and more.
This page covers practical techniques for writing better specs and using them effectively. For format reference, see UI Spec and Data Spec. For methodology overview, see How It Works.
Progressive Precision
You do not need to specify everything at once. Start rough, add detail where it matters. Different blocks — and even different screens — can be at different precision levels within the same spec.
Level 0: Exploration
Screens with titles and purposes only. No blocks, no theme. Use this when you are still figuring out what the app needs.
screens:
user_profile:
title: User Profile
purpose: "User profile page"Level 1: Structure
Add blocks with purposes and basic columns. Optionally add theme. The structure is visible, but visual details are left to AI.
screens:
user_profile:
title: User Profile
purpose: "User profile page"
blocks:
- id: avatar_section
purpose: "User avatar, name, and bio"
- id: edit_button
purpose: "Edit profile button"Level 2: Detailed
Add palette, templates, navigation. Write detailed purposes with behavior. Use nested blocks where structure matters.
metadata:
theme: "modern minimal interface"
palette:
primary: "soft blue gradient"
accent: "warm orange"
screens:
user_profile:
title: User Profile
purpose: "User profile page with editable fields"
blocks:
- id: avatar_section
purpose: "User profile card with hover effect"
blocks:
- id: avatar
purpose: "Circular user photo, 120px, clickable for upload"
- id: info
purpose: "Name (bold, large text) and bio (multiline, editable)"
- id: edit_button
purpose: "Edit button, top-right, opens modal_edit_profile"Level 3: Full Control
Add exact sizes, spacing, colors, animations, transitions. The purpose becomes an implementation-ready specification.
- id: avatar
purpose: "120px circle, border 2px white, shadow-md,
hover scale 1.05 transition 200ms, click opens file upload dialog"
- id: name
purpose: "h2, text-primary, 24px bold, line-height 1.2"
- id: bio
purpose: "p, text-dimmed, 16px, max 3 lines ellipsis, margin-top 12px"
- id: edit_button
purpose: "absolute top-right, padding 10px 20px, border-radius 12px,
glass-secondary background, hover brightness 110%, transition 150ms,
onClick opens modal_edit_profile"Mixing Levels
You do not need to apply the same level everywhere. A common approach: keep most screens at L1–L2, and only go to L3 for the screens or blocks that need pixel-level consistency.
# L1 — still exploring this screen
settings:
title: Settings
purpose: "Application settings and preferences"
blocks:
- id: general
purpose: "General settings section"
- id: notifications
purpose: "Notification preferences"
# L3 — this screen needs exact control
dashboard:
title: Dashboard
purpose: "Main dashboard with KPI cards"
blocks:
- id: stats
purpose: "4 cards in responsive grid, each: width 25%, min-width 200px,
padding 20px, glass-primary background, rounded-xl, shadow-lg.
Large number (32px bold), label (14px text-dimmed), flat white icon (24px).
Hover: scale 1.02, transition 200ms ease-out."Purpose Text vs Nested Blocks
There are two ways to add precision to a block, and they complement each other.
Everything in Purpose
Put all detail in the purpose text. Works well for simple components and quick iteration.
- id: user_card
purpose: "Card with circular avatar (80px), name (bold), role (dimmed), and contact button (primary)"Nested Structure
Break the block into child blocks. Works well for complex components, when individual parts need separate control, or when you want AI to treat each part as a distinct element.
- id: user_card
purpose: "User card with hover effect, rounded corners"
blocks:
- id: avatar
purpose: "Circular photo, 80px"
- id: name
purpose: "User name, bold"
- id: role
purpose: "User role, dimmed text"
- id: contact_button
purpose: "Contact button, primary style"When to Use Which
Use purpose text when the block is simple, self-contained, or you are iterating quickly. Use nested blocks when the block has multiple distinct parts that need separate control, when you want to reference individual parts in behavior descriptions, or when the block is complex enough that a single purpose becomes hard to read.
You can combine both: put container-level styles in the parent purpose, and content details in child blocks.
- id: user_card
purpose: "Glass card, padding 20px, rounded-xl, hover shadow transition 200ms"
blocks:
- id: avatar
purpose: "Circular photo, 80px, border 2px white"
- id: info
purpose: "Name (bold) and role (dimmed), stacked vertically"Behavioral Specification
Purpose describes not just appearance, but behavior — interactions, state changes, animations, and relationships between blocks.
Interactions
purpose: "Button 'Create Task', onClick opens modal_create_task"
purpose: "Task row, click navigates to task_detail screen"
purpose: "Dropdown menu, appears on hover, closes on blur"State Variations
purpose: "Task list with filters. Empty state: illustration + 'No tasks yet' + create button"
purpose: "Save button, disabled until form is valid, shows spinner during save"
purpose: "Notification badge, hidden when count is 0, red pulse animation when new"Animations and Transitions
purpose: "Logo text where last 3 characters change font style every 3 seconds"
purpose: "Card grid, items enter with staggered fade-in 100ms delay each"
purpose: "Sidebar, slides from left 300ms ease-out, overlay dims background"Cross-Block Relationships
# Block A
- id: emotion_picker
purpose: "User selects emotion: happy, sad, angry, surprised"
# Block B
- id: character_avatar
purpose: "Character appearance changes based on selected emotion in emotion_picker"AI agents understand these descriptions and generate appropriate event handling, state management, and animation code.
Integrated Specs: Data + UI
When a project has both a Data Spec and a UI Spec, AI agents understand the connection between them semantically. You do not need to use formal references like Entity.field in your purposes — just describe what the screen should do in natural language, and the agent figures out which entities and fields are involved.
How It Works in Practice
You write the UI Spec the same way you normally would — in natural language, describing intent and behavior.
# Data Spec defines:
entities:
task:
fields:
title:
type: string
required: true
status:
type: task_status
due_date:
type: datetime
assignee_id:
type: uuid
fk: user.id
# UI Spec — just describe what the screen does:
- id: task_form
purpose: "Form for creating a new task. Fields: title, priority, due date, assignee selector with search"When you give both specs to an AI agent, the agent reads the Data Spec and understands that title is a required string, status is an enum with specific values, due_date is a datetime, and assignee_id is a reference to the user entity. It generates appropriate controls, validation, and data binding — without you having to spell out each reference explicitly.
Why Two Specs
The Data Spec gives the agent knowledge about the domain: what entities exist, what types and constraints their fields have, how entities relate to each other. The UI Spec gives the agent knowledge about the interface: what screens exist, what each screen shows, how it behaves.
Together they form a complete, compact description of the application. The agent connects the two layers on its own.
Spec as Verification Layer
A spec sits between your idea and the code. When the implementation drifts from the intent, the spec is the anchor.
Debug by Reference
When an AI agent produces something wrong, do not explain the fix in natural language. Point back to the spec.
# Instead of this:
"No, the header should have the logo on the left and navigation on the right,
and the background should be the glass style we discussed"
# Do this:
"Check the spec for header_bar and fix the implementation to match"The spec is unambiguous. Natural language is not.
When you use HTML implementations generated by Spec Canvas as references, this mapping is even more precise. Every block in the HTML is marked with a ui-spec-block-id attribute that matches the block ID in the spec. The agent can look at the HTML, find the exact block, check the spec for its intended purpose, and fix the implementation — without you having to explain which part of the page you are talking about.
Spec Reset
When an agent drifts too far after many iterations, reset to the spec instead of adding more corrections.
- Stop iterating.
- Update the spec to reflect what you actually want.
- Generate a fresh implementation from the updated spec.
- Continue from a clean baseline.
This is cheaper than layering fix on top of fix.
Spec Diff
When working with an AI agent that already knows the codebase, you do not need to send the full spec every time. Send only the change.
Full Spec vs Diff
Full spec — give this to a new agent that has no context. The agent reads the entire spec and builds from scratch.
Spec diff — give this to an agent that already has the codebase. The diff is a ready-made task.
# Spec diff — tells the agent exactly what changed:
blocks:
- id: problem_statement
- purpose: "Bullet points: 'Design upfront', 'Hope it works', 'Discover it's wrong'"
+ purpose: "Flow diagram: Describe → See → Implement"When to Use Which
| Situation | What to send |
|---|---|
| New agent, no context | Full spec |
| Same session, iterating | Spec diff |
| Agent already knows the codebase | Spec diff |
| Handing off to a colleague | Spec diff + comment |
A spec diff turns "check the whole spec and figure out what changed" into "here is the task, apply it." One step instead of five.