Components Upgrade guide

Upgrading versions

v2.8.4 to v2.9.0

Petal Components has been upgraded to Tailwind 4. Some utilities have been removed or renamed. See the Tailwind upgrade guide for more information.

To upgrade to Tailwind 4 in your project, make sure you update mix.exs:

{:tailwind, "~> 0.3", runtime: Mix.env() == :dev}

And config.exs (note that the paths have changed and that you need 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__)
  ]

Don’t forget to run:

mix tailwind.install

tailwind.config.js is now considered legacy and is no longer automatically loaded. However, it is still supported and you can manually load it by adding the following to your app.css:

@config "../tailwind.config.js";

Now you should be able to follow the Tailwind upgrade guide to update your project.

Petal Components CSS configuration

Here are some tips on how to integrate Petal Components if you are no longer using the tailwind.config.js file.

To reference Petal Components CSS and include it as a source for Tailwind utility classes, use the @source and @import directives:

@import "tailwindcss";

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

The CSS file equivalent of darkMode: 'class' is:

@custom-variant dark (&:where(.dark, .dark *));

In Tailwind 4 buttons now use cursor: default instead of cursor: pointer. If you would like the old behaviour:

@layer base {
  /* Use the pointer for buttons */
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}

To configure Petal Component colours add an @import:

@import "./colors.css";

Then create the colors.css 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);
}

To add the “typography” and “form” plug-ins:

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

To re-create the heroicons JavaScript:

@plugin "./tailwind_heroicons.js";

Then create the tailwind_heroicons.js file:

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(/
?
|
/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 },
  );
});

Please see our upgrade guide for instructions on upgrading previous versions.