Skip to content

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.

PHP
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:

Argument Behavior
string Added as-is.
int / float Cast to string and added.
Sequential array Each element is processed recursively.
key => bool array Each key is added when its value is truthy.
null, false, 0, '' Ignored.
PHP
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.

PHP
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.

PHP
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.

PHP
use function TailwindPHP\join;

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

Which should I use?

Function Constructs (conditionals, arrays) Resolves conflicts Use when
cn() yes yes Default choice — building class strings that may conflict.
merge() no yes Input is plain strings; you only need de-conflicting.
join() yes no You know there are no conflicts and want minimal work.

Imports

PHP
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).