Tailwind merge for Phoenix / Elixir

Problem

I am working on an open source project OrangeCMS using Phoenix LiveView. I am building some reusable components which supports custom class. But many time custom class conflict with default class and I didn’t get expected result.

For example:

attr :class, :string, default: nil
slot :inner_block, required: true
def my_component(assigns) do
  ~H"""
   <div class={["bg-green-500", @class]}>
    <%= render_slot(@inner_block) %>
   </div>
  """
end

and use it

<.my_component class="bg-red-500"> HELLO </.my_component>

The result is not red-500 as you might expected.

Solution

I searched for a solution, and I found tailwind-merge which is written in JavaScript. Basically it finds conflicted classes and resolve the conflict. For example:

twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]')
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'

I was thinking of porting this library to Elixir. But first, I searched on hex.pm and Surprising. I found two packages that support merging tailwind classes: twix and tails

First, I try Twix but it doesn’t support custom color classes and raise exception.

Then I try Tails , and with some little configuration, it works perfectly.

  1. Add Tails to your dependencies

{:tails, "~> 0.1.5"}

  1. Add custom colors.json files to /assets/ or /priv directory
{
  "primary": "orange",
  "secondary": "green"
}
  1. Add Tails config
config :tails, colors_file: Path.join(File.cwd!(), "assets/colors.json")
  1. Update code

Wrap class list with Tails.classes

class={Tails.classes(["bg-green-500", @class])}

attr :class, :string, default: nil
slot :inner_block, required: true
def my_component(assigns) do
  ~H"""
   <div class={Tails.classes(["bg-green-500", @class])}>
    <%= render_slot(@inner_block) %>
   </div>
  """
end

Conclusion

Thank to **Zach Daniel **, it’s now easier for us to resolve tailwind class conflict in Elixir. And before roll out my own solution, I should take a second to search on Hex.pm , it’s amazing that most of what I need was already implemented by Elixir community. Send my regards to all of those who spend their precious hours to make my life easier.