Comparison
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 your agent stops guessing.
MIT licensed, nothing to sign up for. Prefer the source? Read it on GitHub
213 components petal_components 4.16 MIT
<Field>
<FieldLabel htmlFor="email">
Email
</FieldLabel>
<Input id="email" type="email" />
<FieldDescription>
We never share it.
</FieldDescription>
<FieldError errors={[errors.email]} />
</Field>
<.field
field={@form[:email]}
type="email"
label="Email"
help_text="We never share it."
/>
JSX
one tag in HEEx
rendered live, version 4.16
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.
Both libraries now talk to your agent over MCP. shadcn has an MCP server for browsing and installing registry items. Ours answers a different question. It hands back the real attrs, slots and enums of the current petal_components release, so your agent writes HEEx against the library instead of guessing from training data.
Connecting it takes one line in your agent, and the steps for each agent live on the library page. Connect your agent
Side by side
The same questions put to both libraries: where the code runs, how it reaches your project, who owns it, and what your agent can ask it. Where shadcn's answer lives in its own docs, the row links there.
| Question | shadcn/ui | petal_components |
|---|---|---|
| Runtime | React | Phoenix LiveView (HEEx) |
| Styling | Tailwind v4 + CSS variables | Tailwind v4 + theme tokens |
| 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
|
| Catalogue | About the same size, listed on their docs index | 213 HEEx components, counted from the published package |
| Underlying primitives | Radix UI for headless behavior | Phoenix.LiveView.JS plus a bundled set of plain JavaScript hooks |
| AI tool integration | MCP server over the registry, for browsing and installing items |
Hosted MCP server at mcp.petal.build, schema introspected from the published Hex package
|
| Works in non-JS views | No (React-only) | Yes (live + dead views) |
| License | MIT | MIT |
shadcn's answers link to its own docs
ours read from petal_components 4.16
In code
A button and a dialog, side by side like the field above. The mental model is identical and the syntax is what your stack uses: where the JSX passes props and composes parts, the HEEx tag takes attributes and an inner block.
The same outline Save button in both, with the variant and the size under the same two names, as props in JSX and as attributes in HEEx. The HEEx also names its colour, since the library's outline defaults to the primary and shadcn's to the neutral.
See the buttons<Button variant="outline" size="sm">
Save
</Button>
<.button variant="outline" size="sm"
color="gray">
Save
</.button>
JSX, attributes in HEEx
Trigger, content and title as parts in JSX. In HEEx a plain button opens the modal by its id, the title and the width are attributes, and the body is the inner block.
See the modal<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogTitle>Edit user</DialogTitle>
{/* form */}
</DialogContent>
</Dialog>
<.button phx-click={show_modal("edit")}>
Open
</.button>
<.modal id="edit" title="Edit user"
max_width="md" hide>
<%!-- form --%>
</.modal>
JSX
a button and one tag in HEEx
Honest tradeoffs
Picking a library for a Phoenix app is mostly picking a runtime, and the table settles that. These are the places the libraries still differ, so you know the trade before you start.
About the same size now: 213 components here, shadcn's core set plus its blocks there. The shape differs more than the count. shadcn ships primitives you compose into a field or a dialog yourself; each petal_components component is one tag with the composition done. The roadmap is public.
shadcn sits on Radix, a deep set of behavioural primitives. Ours use Phoenix.LiveView.JS, with a bundled set of plain JavaScript hooks for the interactive ones: lighter, and closer to the runtime. For the forms, tables, modals and menus of a SaaS app that is the right trade. For unusual keyboard patterns in deeply nested menus you may do some of the work yourself.
shadcn's community is larger, with themes and recipes to match. Ours is smaller and built by the same team as the library: a playground to try every component, the Petal Design skill so your agent writes to the system's doctrine, Typeset for type, an MCP server that serves the real schema, and Petal Pro, a SaaS boilerplate built on it.
petal_components is MIT licensed and on Hex. Choosing it gets you:
Prefer the source? Read it on GitHub
3,383 developers on Petal 213 components petal_components 4.16