TailwindCombine
Merge Tailwind CSS classes in Elixir without style conflicts.
Inspired by the upstream JS package,
tailwind-merge.
Why?
Overriding Tailwind CSS classes is unintuitive.
Due to the way the CSS Cascade works, the order of CSS styles applied on an element isn't based on the order of given classes, but the order in which CSS styles appear in CSS stylesheets. Because of that, when using Tailwind CSS classes which involve the same styles (we call them conflicting classes), the final styles are indeterminate.
<% # Is it red or green? It's hard to say. %>
<div class={["h-12 bg-red-500", "bg-green-500"]}></div>TailwindCombine solves this problem by overriding conflicting classes and keeping everything else untouched.
<div class={TailwindCombine.merge(["h-12 bg-red-500", "bg-green-500"])}></div>
<% # equals to %>
<div class="h-12 bg-green-500"></div>Status
-
Supports Tailwind CSS v4 merge semantics, with coverage centered on the same core behavior tested by
tailwind-merge. - Tracks the upstream JS implementation with a growing parity test suite.
-
Preserves the Elixir package's existing config model via
use TailwindCombineandTailwindCombine.Config.new/1.
Installation
Add :tailwind_combine to the list of dependencies in mix.exs:
def deps do
[
{:tailwind_combine, <requirement>}
]
endRelease instructions live in RELEASING.md.
Usage
Use the default config directly:
TailwindCombine.merge("p-2 p-4")
# "p-4"
TailwindCombine.merge(["p-2", nil, ["hover:p-2", "hover:p-4"]])
# "p-2 hover:p-4"This is usually enough for Phoenix components and HEEx templates:
<button class={TailwindCombine.merge(["px-3 py-2", @class, @active && "bg-blue-600"])}>
Save
</button>Helper Module
If you want a shorter local helper, define one in your application:
defmodule DemoWeb.ClassHelper do
use TailwindCombine
end
That gives you DemoWeb.ClassHelper.tw/1:
DemoWeb.ClassHelper.tw("text-sm text-lg")
# "text-lg"Custom Config
You can keep the default config shape and override parts of it:
defmodule DemoWeb.ClassHelper do
colors = TailwindCombine.Config.colors() ++ ["primary", "secondary"]
class_groups = TailwindCombine.Config.class_groups(colors: colors)
use TailwindCombine,
as: :merge_class,
config: TailwindCombine.Config.new(class_groups: class_groups)
end
DemoWeb.ClassHelper.merge_class("text-red-500 text-primary")
# "text-primary"Prefix support is also available:
defmodule DemoWeb.Tw do
use TailwindCombine,
config: TailwindCombine.Config.new(prefix: "tw")
end
DemoWeb.Tw.tw("tw:p-2 tw:p-4")
# "tw:p-4"Notes
- Unknown classes pass through unchanged.
-
Nested class lists,
nil, andfalsevalues are ignored in list input. -
The library focuses on merge behavior parity with the upstream JS package,
tailwind-merge; JS-specific factory APIs are not part of the Elixir public API.
For more detail, see the documentation.