Petal Pro is the full SaaS app this is built for

Auth, billing, admin, and Claude Code integration included. One purchase, unlimited projects.

Install Petal Components with Tailwind 3

Legacy install path. Tailwind 3 + petal_components 2.x.

This page is for projects that haven’t upgraded to Tailwind 4 yet. petal_components 3.x requires Tailwind 4, so on Tailwind 3 you’re pinned to the 2.x line. New components and the AI-driven install path ship on 3.x — to get those, upgrade to Tailwind 4 instead.

Want the two-step AI install (claude mcp add petal + "install petal_components")? Upgrade to Tailwind 4 first — the MCP server installs petal_components 3.x, which requires Tailwind 4.

Manual install (Tailwind 3)

New projects

You can use our free Phoenix basic boilerplate that has the components pre-installed (Tailwind 4 by default — switch the deps if you really want Tailwind 3).

Or use Petal Pro, the paid SaaS boilerplate with auth, multi-tenancy, billing, and more.

How to install Tailwind

Follow this guide for Tailwind 3 on Phoenix: Tailwind 3 installation guide for Phoenix.

How to install Petal Components

Add dependency

mix.exs
defp deps do
[
{:petal_components, "~> 2.0"},
]
end

Modify the tailwind.config.js

You will need to open your tailwind.config.js and make two changes:

  1. include the Petal Components folder in the content array
  2. extend the color palette in the theme.extend.colors object
tailwind.config.js
const colors = require("tailwindcss/colors"); // <-- ADD THIS LINE
const plugin = require("tailwindcss/plugin");
module.exports = {
content: [
"../lib/*_web.ex",
"../lib/*_web/**/*.*ex",
"./js/**/*.js",
"../deps/petal_components/**/*.*ex", // <-- ADD THIS LINE
],
darkMode: "class",
theme: {
extend: {
// ADD THESE COLORS (can pick different ones from here: https://tailwindcss.com/docs/customizing-colors)
colors: {
primary: colors.blue,
secondary: colors.pink,
success: colors.green,
danger: colors.red,
warning: colors.yellow,
info: colors.sky,
// Options: slate, gray, zinc, neutral, stone
gray: colors.gray,
},
},
},
plugins: [
require("@tailwindcss/forms"),
plugin(({ addVariant }) =>
addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])
),
plugin(({ addVariant }) =>
addVariant("phx-click-loading", [
".phx-click-loading&",
".phx-click-loading &",
])
),
plugin(({ addVariant }) =>
addVariant("phx-submit-loading", [
".phx-submit-loading&",
".phx-submit-loading &",
])
),
plugin(({ addVariant }) =>
addVariant("phx-change-loading", [
".phx-change-loading&",
".phx-change-loading &",
])
),
],
};

Include the CSS

app.css
@import "tailwindcss/base";
@import "../../deps/petal_components/assets/default.css";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

Import the components

This allows them to be called anywhere in your views.

<your_app>_web.ex
defmodule YourAppWeb
...
defp html_helpers do
quote do
...
use PetalComponents
end
end

Core Components

With Phoenix 1.6 or below, you may have a modal/1 function defined already, which will cause a clash with PetalComponents.Modal.modal/1. Check in live_helpers.ex and remove it.

Petal Components work fine with Phoenix 1.7 - there just will be some naming conflicts as the Phoenix generator now creates a file called core_components.ex, which has some function components defined in there.

To fix, go to the generated core_components.ex file and rename or remove the following functions: modal, button, table, input, label, flash and flash_group.

Unfortunately, this means the generators like mix phx.gen.live won’t work properly - we’ve documented some workarounds. If you want generators for Petal Components, look into buying Petal Pro.

For a full upgrade commit of Phoenix 1.6 to 1.7 you can see here how we did it with Petal Boilerplate.

If you want to pick and choose which components to use, you could import only the ones you want:

# Instead of `use PetalComponents`, you could do this and delete any you don't want:
import PetalComponents.{
Alert,
Badge,
Button,
Container,
Dropdown,
Form,
Loading,
Typography,
Avatar,
Progress,
Breadcrumbs,
Pagination,
Link,
Modal,
SlideOver,
Tabs,
Card,
Table,
Accordion,
Icon
}

Optionally set a translator for form errors

If you want to translate your form errors to anything other than English you will need to provide Petal Components with your translater function.

config.exs
config :petal_components, :error_translator_function, {<YourApp>Web.ErrorHelpers, :translate_error}

How to install Alpine JS

Alpine JS is the default JS library for a couple of components like Dropdown and Accordion. Though you can opt out and us Liveview.JS (only works in live views) - see the js_lib attribute on the components.

Add to your root.html.heex file:

root.html.heex
<head>
<!-- For accordion -->
<script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js">
</script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js">
</script>
</head>

How to install VSCode snippets

Namespacing

You can namespace all the components under any module you like. For example:

# in your_app_web.ex
defp html_helpers do
quote do
# HTML escaping functionality
import Phoenix.HTML
# Core UI components and translation
import YourAppWeb.CoreComponents
import YourAppWeb.Gettext
# Shortcut for generating JS commands
alias Phoenix.LiveView.JS
# Routes generation with the ~p sigil
unquote(verified_routes())
# Petal Components
defmodule PC do
defdelegate accordion(assigns), to: PetalComponents.Accordion
defdelegate alert(assigns), to: PetalComponents.Alert
defdelegate avatar(assigns), to: PetalComponents.Avatar
defdelegate badge(assigns), to: PetalComponents.Badge
defdelegate breadcrumbs(assigns), to: PetalComponents.Breadcrumbs
defdelegate button(assigns), to: PetalComponents.Button
defdelegate icon_button(assigns), to: PetalComponents.Button
defdelegate card(assigns), to: PetalComponents.Card
defdelegate container(assigns), to: PetalComponents.Container
defdelegate dropdown(assigns), to: PetalComponents.Dropdown
defdelegate form_label(assigns), to: PetalComponents.Form
defdelegate field(assigns), to: PetalComponents.Field
defdelegate icon(assigns), to: PetalComponents.Icon
defdelegate input(assigns), to: PetalComponents.Input
defdelegate a(assigns), to: PetalComponents.Link
defdelegate spinner(assigns), to: PetalComponents.Loading
defdelegate modal(assigns), to: PetalComponents.Modal
defdelegate pagination(assigns), to: PetalComponents.Pagination
defdelegate progress(assigns), to: PetalComponents.Progress
defdelegate rating(assigns), to: PetalComponents.Rating
defdelegate slide_over(assigns), to: PetalComponents.SlideOver
defdelegate table(assigns), to: PetalComponents.Table
defdelegate td(assigns), to: PetalComponents.Table
defdelegate tr(assigns), to: PetalComponents.Table
defdelegate th(assigns), to: PetalComponents.Table
defdelegate tabs(assigns), to: PetalComponents.Tabs
defdelegate h1(assigns), to: PetalComponents.Typography
defdelegate h2(assigns), to: PetalComponents.Typography
defdelegate h3(assigns), to: PetalComponents.Typography
defdelegate h4(assigns), to: PetalComponents.Typography
defdelegate h5(assigns), to: PetalComponents.Typography
defdelegate p(assigns), to: PetalComponents.Typography
defdelegate prose(assigns), to: PetalComponents.Typography
defdelegate ol(assigns), to: PetalComponents.Typography
defdelegate ul(assigns), to: PetalComponents.Typography
end
end
end

Then you can call components like so:

<.form for={@form} phx-submit="on_submit" phx-target={@myself}>
<PC.field field={@form[:title]} />
<PC.field field={@form[:description]} />
<PC.button label="Save" />
</.form>