Components Install

Welcome to Petal Components 👋

A versatile set of beautifully styled components for you to use in your next Phoenix project.

Installation

New projects

You can use our free Phoenix basic boilerplate that has the components pre-installed.

Or you can use our paid boilerplate (Petal Pro), which provides additional functionality like authentication, email components, layouts and more.

How to install Tailwind

Firstly, follow this guide to install Tailwind: Tailwind installation guide for Phoenix.

How to install Petal Components

Add dependency

elixir
          defp deps do
  [
    {:petal_components, "~> 3.0"},
  ]
end

        

Configure and Install Tailwind 4.0.9

The Tailwind 4 setup instructions for Phoenix installs Tailwind 4.0.0. Make sure that it’s set to 4.0.9 (or above):

config :tailwind,
  version: "4.0.9",
   default: [
    args: ~w(
      --input=assets/css/app.css
      --output=priv/static/assets/app.css
    ),
    cd: Path.expand("..", __DIR__)
  ]

Run the following command to make sure that the correct version of Tailwind is installed:

mix tailwind.install

CSS Configuration

In Tailwind 4, tailwind.config.js is now considered legacy and is no longer automatically loaded. The default mechanism for configuration is via CSS.

To add Petal Components, update the app.css file:

@import "tailwindcss";

@source "../../deps/petal_components/**/*.*ex";
@import "../../deps/petal_components/assets/default.css";

@import "./colors.css";

@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

@plugin "./tailwind_heroicons.js";

Let’s go through this line-by-line:

@import "tailwindcss";

This is the default configuration for a new project with Tailwind 4.

@source "../../deps/petal_components/**/*.*ex";

The @source directive instructs Tailwind to include the Petal Components folder when scanning for CSS utilities. This ensures that any Tailwind utility used by a Petal Component is part of the JIT compilation process.

@import "../../deps/petal_components/assets/default.css";

Adds the default styles for Petal Components.

@import "./colors.css";

This @import directive is for a file that defines custom colors for Petal Components. You’ll need to create colors.css. See “Creating additional files” for more information.

@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

Adds the Tailwind 4 Typography and Forms plugins. Used by the following custom plugin:

@plugin "./tailwind_heroicons.js";

tailwind_heroicons.js is another file that you’ll need to create. Again, see “Creating additional files” for more information.


Creating additional files

app.css refers to colors.css. You’ll need to create this file:

@theme inline {
  --color-primary-50: var(--color-blue-50);
  --color-primary-100: var(--color-blue-100);
  --color-primary-200: var(--color-blue-200);
  --color-primary-300: var(--color-blue-300);
  --color-primary-400: var(--color-blue-400);
  --color-primary-500: var(--color-blue-500);
  --color-primary-600: var(--color-blue-600);
  --color-primary-700: var(--color-blue-700);
  --color-primary-800: var(--color-blue-800);
  --color-primary-900: var(--color-blue-900);
  --color-primary-950: var(--color-blue-950);

  --color-secondary-50: var(--color-pink-50);
  --color-secondary-100: var(--color-pink-100);
  --color-secondary-200: var(--color-pink-200);
  --color-secondary-300: var(--color-pink-300);
  --color-secondary-400: var(--color-pink-400);
  --color-secondary-500: var(--color-pink-500);
  --color-secondary-600: var(--color-pink-600);
  --color-secondary-700: var(--color-pink-700);
  --color-secondary-800: var(--color-pink-800);
  --color-secondary-900: var(--color-pink-900);
  --color-secondary-950: var(--color-pink-950);

  --color-success-50: var(--color-green-50);
  --color-success-100: var(--color-green-100);
  --color-success-200: var(--color-green-200);
  --color-success-300: var(--color-green-300);
  --color-success-400: var(--color-green-400);
  --color-success-500: var(--color-green-500);
  --color-success-600: var(--color-green-600);
  --color-success-700: var(--color-green-700);
  --color-success-800: var(--color-green-800);
  --color-success-900: var(--color-green-900);
  --color-success-950: var(--color-green-950);

  --color-danger-50: var(--color-red-50);
  --color-danger-100: var(--color-red-100);
  --color-danger-200: var(--color-red-200);
  --color-danger-300: var(--color-red-300);
  --color-danger-400: var(--color-red-400);
  --color-danger-500: var(--color-red-500);
  --color-danger-600: var(--color-red-600);
  --color-danger-700: var(--color-red-700);
  --color-danger-800: var(--color-red-800);
  --color-danger-900: var(--color-red-900);
  --color-danger-950: var(--color-red-950);

  --color-warning-50: var(--color-yellow-50);
  --color-warning-100: var(--color-yellow-100);
  --color-warning-200: var(--color-yellow-200);
  --color-warning-300: var(--color-yellow-300);
  --color-warning-400: var(--color-yellow-400);
  --color-warning-500: var(--color-yellow-500);
  --color-warning-600: var(--color-yellow-600);
  --color-warning-700: var(--color-yellow-700);
  --color-warning-800: var(--color-yellow-800);
  --color-warning-900: var(--color-yellow-900);
  --color-warning-950: var(--color-yellow-950);

  --color-info-50: var(--color-sky-50);
  --color-info-100: var(--color-sky-100);
  --color-info-200: var(--color-sky-200);
  --color-info-300: var(--color-sky-300);
  --color-info-400: var(--color-sky-400);
  --color-info-500: var(--color-sky-500);
  --color-info-600: var(--color-sky-600);
  --color-info-700: var(--color-sky-700);
  --color-info-800: var(--color-sky-800);
  --color-info-900: var(--color-sky-900);
  --color-info-950: var(--color-sky-950);

  --color-gray-50: var(--color-slate-50);
  --color-gray-100: var(--color-slate-100);
  --color-gray-200: var(--color-slate-200);
  --color-gray-300: var(--color-slate-300);
  --color-gray-400: var(--color-slate-400);
  --color-gray-500: var(--color-slate-500);
  --color-gray-600: var(--color-slate-600);
  --color-gray-700: var(--color-slate-700);
  --color-gray-800: var(--color-slate-800);
  --color-gray-900: var(--color-slate-900);
  --color-gray-950: var(--color-slate-950);
}

It also refers to tailwind_heroicons.js:

const plugin = require("tailwindcss/plugin");
const fs = require("fs");
const path = require("path");

// Embeds Heroicons (https://heroicons.com) into your app.css bundle
// See your `CoreComponents.icon/1` for more information.
module.exports = plugin(function ({ matchComponents, theme }) {
  let iconsDir = path.join(__dirname, "../../deps/heroicons/optimized");
  let values = {};
  let icons = [
    ["", "/24/outline"],
    ["-solid", "/24/solid"],
    ["-mini", "/20/solid"],
    ["-micro", "/16/solid"],
  ];
  icons.forEach(([suffix, dir]) => {
    fs.readdirSync(path.join(iconsDir, dir)).forEach((file) => {
      let name = path.basename(file, ".svg") + suffix;
      values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
    });
  });
  matchComponents(
    {
      hero: ({ name, fullPath }) => {
        let content = fs
          .readFileSync(fullPath)
          .toString()
          .replace(/\r?\n|\r/g, "");
        let size = theme("spacing.6");
        if (name.endsWith("-mini")) {
          size = theme("spacing.5");
        } else if (name.endsWith("-micro")) {
          size = theme("spacing.4");
        }
        return {
          [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
          "-webkit-mask": `var(--hero-${name})`,
          mask: `var(--hero-${name})`,
          "mask-repeat": "no-repeat",
          "background-color": "currentColor",
          "vertical-align": "middle",
          display: "inline-block",
          width: size,
          height: "1lh",
        };
      },
    },
    { values },
  );
});

Import the components

This allows them to be called anywhere in your views.

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

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

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