TM - Tailwind Merge for Elixir

Pure Elixir utility for merging Tailwind CSS classes with conditional support. Zero dependencies, fast native BEAM performance.

Based on the JavaScript libraries tailwind-merge and clsx.

Features

Installation

Add tm to your dependencies in mix.exs:

def deps do
  [
    {:tm, path: "../tm_tailwind_merge"}
  ]
end

Usage

Basic Merge

# Later classes win when they conflict
TM.merge("text-red-500 text-blue-500")
#=> "text-blue-500"

TM.merge("p-4 p-2")
#=> "p-2"

# Non-conflicting classes are preserved
TM.merge("p-4 px-2")
#=> "p-4 px-2"

# Modifiers create separate scopes
TM.merge("hover:bg-red-500 hover:bg-blue-500")
#=> "hover:bg-blue-500"

TM.merge("bg-red-500 hover:bg-red-500")
#=> "bg-red-500 hover:bg-red-500"

With Conditionals (tc = tailwind conditionals)

# Map syntax
TM.tc(["p-4", %{"bg-red-500" => is_error, "bg-green-500" => is_success}])
#=> "p-4 bg-red-500" (when is_error is true)

# Keyword syntax
TM.tc(["flex", hidden: should_hide])
#=> "flex" or "hidden" depending on should_hide

# Handles nil/false from if() expressions
TM.tc(["base", if(false, do: "hidden")])
#=> "base"

# Multiple conditions
TM.tc([
  "px-4 py-2",
  %{
    "bg-blue-500" => variant == :primary,
    "bg-red-500" => variant == :danger
  }
])

In Phoenix Components

def button(assigns) do
  ~H"""
  <button class={TM.tc([
    "inline-flex items-center justify-center font-medium transition-all",
    "px-3 py-1.5 text-sm": @size == :sm,
    "px-4 py-2 text-base": @size == :md,
    "px-6 py-3 text-lg": @size == :lg,
    "bg-blue-500 text-white hover:bg-blue-600": @variant == :primary,
    "bg-red-500 text-white hover:bg-red-600": @variant == :danger,
    "opacity-50 cursor-not-allowed": @disabled
  ])}>
    <%= render_slot(@inner_block) %>
  </button>
  """
end

Just clsx (no merge)

If you only need conditional class building without conflict resolution:

TM.clsx(["foo", %{"bar" => true, "baz" => false}])
#=> "foo bar"

# Note: clsx doesn&#39;t merge conflicts
TM.clsx(["p-4", "p-2"])
#=> "p-4 p-2"

API

Function Description
TM.tc/1 Conditionals + merge (main function)
TM.merge/1 Merge only (string or list input)
TM.clsx/1 Conditionals only (no merge)

How It Works

TM is a pure Elixir port of the tailwind-merge and clsx algorithms:

  1. clsx - Recursively processes inputs (strings, lists, maps, keyword tuples), filters falsy values, and joins with spaces
  2. tailwind-merge - Parses classes into components (modifiers + base class), identifies class groups, processes right-to-left keeping only the last class per group

Class Groups

Classes are organized into groups based on the CSS property they modify:

Groups also define conflicts (e.g., p-* overrides px-*, py-*, etc.)

Performance

Pure Elixir implementation with no external process calls:

Requirements

License

MIT