Open source

Every component is one HEEx tag. Nothing for your agent to invent.

213 components for Phoenix LiveView on Tailwind v4, a test file behind every one, 1.4M downloads. Your agent reads the schema from the MCP server and writes the tag: a handful of tokens, the same on every screen, instead of a block of Tailwind it will never write the same way twice.

Free account, no credit card. Prefer email?

213 components petal_components 4.16 1k stars on GitHub MIT

A billing dashboard built out of the library: a New invoice button in the bar, three stats (revenue $48,200, outstanding $3,230, paid on time 92%), a table of four invoices, each with a status badge and an Edit button, the morning's three events (a reminder sent for INV-1040, Northwind opening INV-1041, INV-1042 paid), and a collected bar at $1,510 of $4,740. Two controls: the primary colour, which every component on the screen follows, and a status filter that narrows the four invoices.
Primary colour
Invoices 4 invoices · $4,740 2 paid · $1,510 1 sent · $480 1 overdue · $2,750
Filter invoices by status

Writing UI

One tag in, one component out.

The invoices table in the dashboard at the top of the page is the first specimen: its file is below, the Amount column left out to fit the panel, and the same call renders live beside it. Every block on this page is real markup from the installed library. Paste one into a template and you get what you see, in both schemes, with focus and keyboard already in it.

invoices_live.heex the whole file
<.table rows={@invoices}>
<:col :let={row} label="Invoice">
{row.ref}
</:col>
<:col :let={row} label="Status">
<.badge dot dot_color={row.tone}
color="gray" variant="soft"
label={row.status} />
</:col>
<:col label="">
<.button size="xs" color="gray"
variant="outline"
label="Edit" />
</:col>
</.table>
petal_components 4.16 three slots
renders as
Invoice Status
INV-1042 Paid
INV-1041 Sent
INV-1040 Overdue
INV-1039 Paid

The table, as a file

Rows in, columns as slots. The badge and the row action are two more tags inside it, and there is no class list anywhere in the file.

See the table
invoice_status.heex
<.stepper steps={@steps} size="sm"
label_placement="bottom" />
<.badge dot dot_color="success"
color="gray" variant="soft" label="Paid" />
<.badge color="gray" variant="soft" label="Draft" />
renders as

Three steps, Draft complete, Sent current and Paid still to come, beside a green Paid badge and a plain Draft one.

Status, on the same dial

A badge and a stepper read the tokens everything else reads, so the colour of "paid" is set once for the whole application.

See the theming contract
save_controls.heex
<.button loading={@saving} label="Saving" />
<.button color="gray" variant="outline" label="Cancel" />
renders as

A button that waits

One boolean swaps the label for a spinner and stops the second click. The waiting state is part of the component, not something you wire up around it.

See the buttons
invite.heex the whole file
<.card>
<.card_content heading="New member">
<.field type="email" name="email"
label="Email" value=""
placeholder="alex@acme.com" />
<.field type="switch"
name="welcome" checked
label="Send a welcome email" />
<.button label="Send invite"
class="w-full" />
</.card_content>
</.card>
petal_components 4.16 two fields
renders as
New member
Email

A form inside a card

Fields carry their own label, error and help text, so a changeset error lands beside the input it belongs to without extra markup.

See the card

Install

Two steps, then it is a prompt.

Step one connects your agent to mcp.petal.build, once, on your machine. Step two is a prompt in any Phoenix project: the agent reads the install steps off the server, patches mix.exs, app.css and your web module, and stops. Nothing is copied out of a repository, so an upgrade is a version bump.

claude mcp add petal --transport http https://mcp.petal.build/mcp
// .cursor/mcp.json
{ "mcpServers": { "petal": { "url": "https://mcp.petal.build/mcp" } } }
# ~/.codex/config.toml
[mcp_servers.petal]
url = "https://mcp.petal.build/mcp"
get_install_instructions the setup steps
list_components the inventory, one line each
get_component one component's full schema
2 · in any project a prompt
install petal_components
petal_components 4.16 mix.exs, app.css and the web module umbrella apps too

Windsurf, Continue and Cline take the same URL in their MCP settings.

Connect it once

Three tools, and the agent picks the one it needs. Drop rules.md into your tool's rules system and it reaches for a component before it reaches for a class list.

Read the rules file
get_component button cropped

The answer opens with the component's full name, then lists each attribute with its type, its default and every value it accepts. The button's colour is a closed list of eleven values and its size a closed list of six.

Introspected from Hex 213 components

The schema, not a memory

Ask for one component and the answer is its attrs, slots, defaults and allowed values. The agent writes against enums it can see, so a made-up colour never reaches your template.

Read the server

Prefer to do it by hand? The README has the manual steps.

The catalogue

Six families, one contract.

213 components, all reading the same tokens for colour, radius and type. The index is the live list: every component on one page, with its own docs page and full source behind the name.

six families petal_components 4.16
Layout and content
The frame of a screen: surfaces, sections, and the states a screen sits in while it waits.
Forms
Every input a Phoenix form binds to, each carrying its own label, error and help text.
Actions
The controls that start something, on their own, grouped, or behind a menu.
Feedback
What the app says back: progress, warnings, and the things that interrupt.
Data display
Rows, figures and identity, for the screens that are mostly other people's data.
Navigation and motion
Getting around, plus the pieces a marketing page needs.
213 components MIT Tailwind v4

What the six families cover

Enough to build a whole application without leaving the library, and nothing that only makes sense on a marketing site.

Browse the index
components on your tokens live
Ready
Account name
Draft Archived
primary radius font

Change the look, not the markup

Set primary, radius and font on the dials, then copy the CSS out. The same three tokens drive every component in your app, so nothing is restyled one screen at a time.

Open the playground

The paid half

Already wired up, if you want that.

Petal Pro is a Phoenix SaaS starting point built on these same components: everything an application needs before the part that is yours, as source you own outright. The best reference there is for composing the library into a working app, because it is one.

  • Auth: password, OAuth, passwordless and 2FA
  • Multi-tenant orgs with role based access
  • Stripe billing, subscriptions and one-time
  • Oban jobs, an admin, and a generator for new screens

See Petal Pro

The library needs no account. Here is what one adds.

Everything above is MIT and needs no sign-in. A free account adds:

  • Full source on every docs example
  • A vote on the components roadmap
  • A builder profile
  • Release news by email

Free account, no credit card. Prefer email?

3,383 developers on Petal 1.4M Hex downloads 606 projects registered