Petal Design

A design system your AI agent actually follows

The Petal stack gives an agent three things: components that already look right, an MCP server with exact schemas, and a skill that carries the design doctrine no schema can. Below, the same settings-page prompt, one attempt each, in a fresh Phoenix app versus the Petal stack. Drag the divider.

fresh phx.new → Petal + MCP + skill verbatim eval output
Settings page built by an agent in a fresh Phoenix app Fresh app
Settings page built by the skill agent in a Petal app Petal stack
The exact prompt both agents received

Build a settings LiveView at /settings: app navbar with the product logo on the left and a theme switcher on the right (System / Light / Dark segmented control); profile section (name, email, avatar); notification toggles; and a billing section with a table of the last 10 invoices (date, amount, status, PDF link) plus a plan card showing seat usage (4 of 5 seats) with a usage progress bar and an upgrade button. Support light and dark mode.

Identical prompt and seed data, one attempt each, screenshots untouched. The two sides are different apps by construction: a just-generated Phoenix 1.8 app on the left, the Petal stack on the right.

One token layer, three guards

An agent writes two kinds of code: calls to components the library ships, and the custom markup in between that no library ships. Petal guards each one differently, and points both at the same tokens. That is why the result coheres instead of being two styles glued together.

YOUR PROMPT “Build a settings page” AI agent writes HEEx calls writes SCHEMA TRUTH MCP server mcp.petal.build resolves attrs, slots, enums Component calls <.card> <.field> <.table> 213 surfaces, already styled Custom markup layout, wrappers, one-offs no library ships this part applies the doctrine DESIGN DOCTRINE petal-design skill ghost ladder · dark pairs · focus ring TOKEN LAYER One cohesive UI light and dark, on brand
Dashed borders mark what the agent consults before it writes. Change the primary ramp, the gray dial or the radius token once, and the shipped components and the hand-written markup move together.

The doctrine, in four moves

Everything below is rendered live by petal_components on this page - not screenshots. Pairs badged "verbatim eval output" are real eval material; the rest are labelled doctrine illustrations. Drag each divider.

Tailwind soup to the system

task fixture → MCP + skill verbatim eval output

Task: convert a hand-rolled invoices view. Before: the fixture - literal zinc palette, raw table, hover:opacity on a solid. After: the skill arm's conversion - table, soft badge, and the confirm becomes a real alert_dialog.

Invoices

Date Amount Status
2026-08-01 $29.00 paid
2026-07-01 $29.00 paid
2026-06-01 $29.00 paid
Before
zinc palette hover:opacity

Invoices

Date Amount Status
2026-08-01 $29.00 paid
2026-07-01 $29.00 paid
2026-06-01 $29.00 paid

Are you sure?

This cannot be undone.

After
soft badge alert_dialog

Measured, not promised

The Dark mode pair above, at the class level. The task: "Add dark mode to this view. Light styling stays exactly as is." Both arms had the component schemas. Only one had the doctrine. Outputs are verbatim.

MCP only verbatim output
<div class="rounded-lg border border-gray-200 bg-white p-6 shadow-xs
  dark:border-gray-700 dark:bg-gray-800">
  <h3 class="font-semibold text-gray-900 dark:text-gray-100">API keys</h3>
  ...
  <input type="text" class="... border-gray-300
    dark:border-gray-600 dark:bg-gray-800" />

Valid classes, mechanical inversion: opaque gray-800 panels, gray-600 borders. It compiles. It is not the system.

MCP + skill verbatim output
<div class="rounded-lg border border-gray-200 dark:border-gray-400/17
  bg-white dark:bg-gray-900 p-6 shadow-xs">
  <h3 class="font-semibold text-gray-900 dark:text-white">API keys</h3>
  ...
  <input type="text" class="... border-gray-300
    dark:border-gray-400/25 dark:bg-gray-400/8" />

The system's dark material: translucent gray-400 surfaces, hairline borders, white headings. Nothing in a schema says this. The skill does.

Schema access closes the API gap: both arms write valid component calls. It does not carry taste. The difference between these two outputs is the entire reason the skill exists, and it is the part of your UI no library can ship: the markup in between.

Three doors, one system

Each piece works alone. Together they are the stack from the diagram.

The components

213 surfaces with focus rings, dark pairs and the radius knob built in.

{:petal_components, "~> 4.15"}
Component docs →

The MCP

Exact attrs, slots and enums for the installed version, resolved live. Never from training data.

claude mcp add petal --transport http \
  https://mcp.petal.build/mcp
About the MCP →

The skill

The doctrine, loaded only when your agent does UI work. Triggers itself; no command to remember.

claude plugin marketplace add \
  petalframework/petal_components
claude plugin install petal-design

Or copy deps/petal_components/skills/petal-design into your project's .claude/skills/.

Rebrand with tokens, not edits

Primary ramp, gray dial, one radius knob, the dark material. Turn them in the playground and watch every component follow. The theme lives in the URL, so a look is a link you can share.

Open the playground →

Point your agent at the system

Free and MIT licensed. Install the components, connect the MCP, drop in the skill, and the next settings page your agent builds looks like the Petal side of the slider above.