Comparison

Petal Components vs shadcn

Short version: if you build in React, use shadcn. If you build in Phoenix, use petal_components. Same composable-primitives philosophy. Different runtime. Same idea about AI: ship the schema so coding assistants stop hallucinating.

The honest verdict

shadcn won because of three decisions: composable primitives you actually own, no monolithic theme system to fight, and a CLI that drops the source into your repo. The result is a component library that feels like a code generator, not a runtime dependency.

petal_components ports that philosophy to Phoenix LiveView. The primitives are HEEx tags (<.button>, <.modal>, <.table>), built on Tailwind v4, and they work in both LiveView and dead views.

The new part is the MCP server. AI coding assistants (Claude Code, Cursor, Windsurf, Codex) can query the actual component schema before they write any HEEx. No more inventing component APIs from training data. shadcn does not ship one yet. We do.

Side by side

  shadcn/ui petal_components
Runtime React Phoenix LiveView (HEEx)
Styling Tailwind 3 + CSS variables Tailwind v4
Distribution CLI copies source into your repo Hex package, mix deps.update
You own the code Yes (copied into your repo) Override CSS classes with the pc-* prefix
Component count ~50 30+ and growing
Underlying primitives Radix UI for headless behavior Alpine.js or Phoenix.LiveView.JS
AI tool integration None (training-data guesses) Hosted MCP server at mcp.petal.build
Works in non-JS views No (React-only) Yes (live + dead views)
License MIT MIT

Why the MCP matters

AI coding assistants are the new package manager. When you ask Claude Code or Cursor to build a settings page, they reach for what they remember from training data. For shadcn that mostly works because the catalogue has been ingested thousands of times. For most component libraries it does not: the agent invents component names, hallucinates attrs, or falls back to raw Tailwind soup.

petal_components ships with a hosted MCP server at mcp.petal.build. AI agents call list_components to see the inventory, then get_component to fetch the real attr and slot schema for whichever one they are about to use. The schema is generated by introspecting Phoenix.Component.__components__/0, so it cannot drift from the actual library.

One install line:

claude mcp add petal --transport http https://mcp.petal.build/mcp

Then drop the canonical rules.md into Cursor, Codex, Continue, or Windsurf and the agent reaches for <.button> by default instead of <div class="px-4 py-2 rounded ...">.

Same shape, different language

A few canonical examples. The mental model is identical. The syntax is what your stack uses.

shadcn (React + JSX)
<Button variant="outline" size="sm">
  Save
</Button>
petal_components (Phoenix + HEEx)
<.button variant="outline" size="sm">
  Save
</.button>
shadcn
<FormField name="email">
  <FormLabel>Email</FormLabel>
  <FormControl>
    <Input type="email" />
  </FormControl>
</FormField>
petal_components
<.field
  field={@form[:email]}
  type="email"
  label="Email"
/>
shadcn
<Dialog>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogTitle>Edit user</DialogTitle>
    {/* form */}
  </DialogContent>
</Dialog>
petal_components
<.modal title="Edit user" max_width="md">
  <!-- form -->
</.modal>

Honest tradeoffs

Catalogue size. shadcn has more components. We are closing the gap on the patterns that actually matter for SaaS apps (the ones AI agents reach for: forms, modals, tables, dropdowns, alerts, navigation), but we are not at parity yet. Roadmap is public.

Behavioral primitives. shadcn is built on Radix, which has been hardened by thousands of teams. Our components use Alpine.js or Phoenix.LiveView.JS, which are simpler but cover a smaller set of edge cases out of the box. For the common patterns this is fine. For unusual accessibility needs in nested keyboard-driven menus, you might do some manual work.

Ecosystem. shadcn has thousands of community recipes, themes, and Figma kits. Our ecosystem is smaller but real: an official Figma kit, VSCode snippets, the MCP, and a production SaaS boilerplate built on top of it.

Install in two steps

Step one runs once. Step two runs in any Phoenix project, ever.

1. Install the MCP server
claude mcp add petal --transport http https://mcp.petal.build/mcp
2. In your Phoenix project, tell your AI:
install petal_components

The agent calls get_install_instructions on the MCP, then patches your mix.exs, app.css, and web module. Works in umbrella apps too. Prefer to install by hand? Manual instructions are on the README.