TailwindPHP
Class Utilities

Class Utilities

Building class attributes in templates means joining strings, toggling classes on conditions, and stopping later utilities from clashing with earlier ones (p-2 and p-4 should not both survive). In JavaScript that work is split across clsx and tailwind-merge. TailwindPHP ships PHP ports of both, so you get the same behavior with no Node.js toolchain and no extra Composer packages.

Three functions cover the spectrum: cn() constructs and de-conflicts, merge() only de-conflicts, and join() only concatenates.

cn()

The recommended utility. It runs clsx-style conditional construction first, then resolves Tailwind conflicts with tailwind-merge — so the last conflicting utility wins.

use function TailwindPHP\cn;

// Conflicts are resolved — px-4 overrides px-2
cn('px-2 py-1', 'px-4');
// => 'py-1 px-4'

// Conditional classes via a key => bool array
cn('btn', ['btn-primary' => true, 'btn-disabled' => false]);
// => 'btn btn-primary'

Accepted arguments

cn() accepts any number of arguments. Each is flattened and filtered with clsx semantics:

ArgumentBehavior
stringAdded as-is.
int / floatCast to string and added.
Sequential arrayEach element is processed recursively.
key => bool arrayEach key is added when its value is truthy.
null, false, 0, ''Ignored.
cn('flex', ['items-center' => true], ['p-4', ['gap-2']], null);
// => 'flex items-center p-4 gap-2'

Component pattern

cn() is what makes a component overridable: merge the component's own classes with whatever the caller passes, and conflicts resolve in the caller's favor.

use function TailwindPHP\cn;

function Card(array $props = []): string {
    $class = cn(
        'rounded-lg border bg-card text-card-foreground shadow-sm',
        $props['class'] ?? null
    );
    return "<div class=\"{$class}\">" . ($props['children'] ?? '') . "</div>";
}

Card(['class' => 'p-6', 'children' => 'Content']);
// <div class="rounded-lg border bg-card text-card-foreground shadow-sm p-6">Content</div>

For declarative, multi-axis component styles, pair cn() with variants().

merge()

Conflict resolution only — no conditional construction. Pass class strings; later classes override earlier ones when they target the same property. Use it when your input is already plain strings.

use function TailwindPHP\merge;

merge('px-2 py-1 bg-red-500', 'px-4 bg-blue-500');
// => 'py-1 px-4 bg-blue-500'

// Conflict resolution is variant-aware
merge('hover:bg-red-500', 'hover:bg-blue-500');
// => 'hover:bg-blue-500'

cn() is merge() composed on top of clsx, so reach for merge() directly only when you do not need the conditional array syntax.

join()

Simple concatenation with no conflict resolution. Falsy values are dropped, but conflicting utilities are all kept. Use it when you know there are no conflicts and want the cheapest option.

use function TailwindPHP\join;

join('foo', 'bar', null, 'baz');
// => 'foo bar baz'

Which should I use?

FunctionConstructs (conditionals, arrays)Resolves conflictsUse when
cn()yesyesDefault choice — building class strings that may conflict.
merge()noyesInput is plain strings; you only need de-conflicting.
join()yesnoYou know there are no conflicts and want minimal work.

Imports

use function TailwindPHP\cn;
use function TailwindPHP\merge;
use function TailwindPHP\join;

All three live in the main TailwindPHP namespace and are bundled with the package. They are PHP ports of clsx (by Luke Edwards) and tailwind-merge (by Dany Castillo).

On this page