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 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

The same email field twice: as shadcn/ui JSX composed from a label, an input, a description and an error, which needs a React app to render, and as one petal_components HEEx tag, rendered live underneath.
form-field.tsx shadcn/ui, React
<Field>
<FieldLabel htmlFor="email">
Email
</FieldLabel>
<Input id="email" type="email" />
<FieldDescription>
We never share it.
</FieldDescription>
<FieldError errors={[errors.email]} />
</Field>
form.heex petal_components, HEEx
<.field
field={@form[:email]}
type="email"
label="Email"
help_text="We never share it."
/>
Five tags in JSX one tag in HEEx rendered live, version 4.16

The honest verdict

Same philosophy, a different runtime.

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

Two ecosystems, row by row.

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.

shadcn/ui and petal_components
shadcn/ui and petal_components compared, row by row
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

Same shape, different language.

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.

A button

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
save-button.tsx shadcn/ui, React
<Button variant="outline" size="sm">
Save
</Button>
save_button.heex petal_components, HEEx
<.button variant="outline" size="sm"
color="gray">
Save
</.button>
Same variant, same size props in JSX, attributes in HEEx

A dialog

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
edit-user.tsx shadcn/ui, React
<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogTitle>Edit user</DialogTitle>
{/* form */}
</DialogContent>
</Dialog>
edit_user.heex petal_components, HEEx
<.button phx-click={show_modal("edit")}>
Open
</.button>
<.modal id="edit" title="Edit user"
max_width="md" hide>
<%!-- form --%>
</.modal>
Four tags in JSX a button and one tag in HEEx

Honest tradeoffs

Where the two still differ.

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.

Catalogue

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.

Primitives

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.

Ecosystem

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.

Building in Phoenix? This is the library for it.

petal_components is MIT licensed and on Hex. Choosing it gets you:

  • One HEEx tag per component
  • Updates from Hex, one command away
  • An MCP server that serves the real schema of the latest release
  • The same tags in live views and dead views

Prefer the source? Read it on GitHub

3,383 developers on Petal 213 components petal_components 4.16