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:
| 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. |
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?
| 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
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).
The @source Directive
Use @source in your input CSS to control which template files are scanned for class names and which candidates are forced in or excluded.
Variants
variants() and compose() — a PHP port of CVA (Class Variance Authority) for declarative, multi-axis component styles built on base, variants, compoundVariants, and defaults.