PHPackages                             piplup/tailwind-merge-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. piplup/tailwind-merge-php

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

piplup/tailwind-merge-php
=========================

PHP port of tailwind-merge: merge Tailwind CSS classes without style conflicts

v1.0.2(3mo ago)010MITPHPPHP &gt;=8.1

Since Apr 13Pushed 1mo agoCompare

[ Source](https://github.com/sadik-malik/tailwind-merge-php)[ Packagist](https://packagist.org/packages/piplup/tailwind-merge-php)[ Docs](https://github.com/sadik-malik/tailwind-merge-php)[ RSS](/packages/piplup-tailwind-merge-php/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (1)Versions (4)Used By (0)

tailwind-merge-php
==================

[](#tailwind-merge-php)

A complete PHP port of [tailwind-merge](https://github.com/dcastil/tailwind-merge) — merge Tailwind CSS class strings without style conflicts.

```
use TailwindMerge\TailwindMerge;

$tw = new TailwindMerge();
echo $tw->merge('px-2 py-1 bg-red-500 hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
```

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Composer (for autoloading and running tests)

---

Installation
------------

[](#installation)

```
composer require piplup/tailwind-merge-php
```

Or clone and run `composer install`.

---

Usage
-----

[](#usage)

### Basic merging

[](#basic-merging)

```
use TailwindMerge\TailwindMerge;

$tw = new TailwindMerge();

// Later class wins
$tw->merge('p-2 p-4');                       // → 'p-4'

// Shorthand overrides individual sides
$tw->merge('px-2 py-2 p-4');                 // → 'p-4'
$tw->merge('border rounded px-2 py-1', 'p-5'); // → 'border rounded p-5'

// Multiple arguments are joined before merging
$tw->merge('text-sm', 'text-lg', 'text-xl'); // → 'text-xl'

// Responsive variants are scoped independently
$tw->merge('md:text-sm md:text-lg');         // → 'md:text-lg'
$tw->merge('hover:p-2 focus:p-4');           // → 'hover:p-2 focus:p-4'  (no conflict)

// Important modifier (!) scopes separately from non-important
$tw->merge('p-2 !p-4');                      // → 'p-2 !p-4'
$tw->merge('!p-2 !p-4');                     // → '!p-4'

// Arbitrary values
$tw->merge('p-4 p-[20px]');                  // → 'p-[20px]'
$tw->merge('bg-red-500 bg-[#abc]');          // → 'bg-[#abc]'

// Arbitrary CSS variables (Tailwind v4 parenthesis syntax)
$tw->merge('bg-red-500 bg-(--brand)');       // → 'bg-(--brand)'
$tw->merge('p-4 p-(--my-space)');            // → 'p-(--my-space)'

// Opacity postfix shorthand
$tw->merge('bg-red-500/50 bg-blue-600');     // → 'bg-blue-600'

// Arbitrary CSS property declarations
$tw->merge('[font-size:1rem] [font-size:2rem]');           // → '[font-size:2rem]'
$tw->merge('[--grid-column-span:12] [--grid-column-span:5]'); // → '[--grid-column-span:5]'
```

### Static helper

[](#static-helper)

A shared no-prefix singleton for when you don't need custom config:

```
TailwindMerge::tw('p-2 p-4');  // → 'p-4'
```

### Join without conflict resolution

[](#join-without-conflict-resolution)

```
// twJoin equivalent: concatenate only, no conflict detection
TailwindMerge::join('px-4 py-2', $isError ? 'text-red-600' : '', null, false);
// → 'px-4 py-2 text-red-600'  (null/false/'' are dropped)
```

### Tailwind v4 variant-style prefix

[](#tailwind-v4-variant-style-prefix)

Configured in CSS as `@import "tailwindcss" prefix(tw);` — classes look like `tw:flex`:

```
$tw = TailwindMerge::withConfig(['prefix' => 'tw']);

$tw->merge('tw:px-2 tw:py-2', 'tw:p-4');         // → 'tw:p-4'
$tw->merge('tw:hover:bg-red-500', 'tw:hover:bg-blue-500'); // → 'tw:hover:bg-blue-500'

// Non-prefixed classes pass through untouched
$tw->merge('tw:p-4 p-2 custom-class');            // → 'tw:p-4 p-2 custom-class'
```

### Tailwind v3 dash-style prefix

[](#tailwind-v3-dash-style-prefix)

Configured in JS as `prefix: 'tw'` in `tailwind.config.js` — classes look like `tw-flex`:

```
$tw = TailwindMerge::withConfig(['prefix' => 'tw-']);

$tw->merge('tw-px-2 tw-py-2', 'tw-p-4');         // → 'tw-p-4'
$tw->merge('hover:tw-bg-red-500', 'hover:tw-bg-blue-500'); // → 'hover:tw-bg-blue-500'

// Non-prefixed classes pass through untouched
$tw->merge('tw-p-4 p-2 custom-class');            // → 'tw-p-4 p-2 custom-class'
```

### Custom class groups

[](#custom-class-groups)

```
$tw = TailwindMerge::withConfig([
    'extend' => [
        'classGroups' => [
            // 'my-size-sm', 'my-size-md', 'my-size-lg' will now conflict
            'my-size' => [['my-size' => ['sm', 'md', 'lg']]],
        ],
        'conflictingClassGroups' => [
            // a my-size-* class will also displace w-* and h-*
            'my-size' => ['w', 'h'],
        ],
    ],
]);

$tw->merge('my-size-sm my-size-lg');  // → 'my-size-lg'
$tw->merge('w-4 h-4 my-size-lg');    // → 'my-size-lg'
```

### Exposing and composing configs

[](#exposing-and-composing-configs)

```
// Inspect the default config
$config = TailwindMerge::getDefaultConfig();

// Compose two configs programmatically (useful for plugins)
$merged = TailwindMerge::mergeConfigs(
    TailwindMerge::getDefaultConfig(),
    ['extend' => ['classGroups' => ['my-group' => [...]]]]
);
$tw = new TailwindMerge($merged);
```

---

Architecture
------------

[](#architecture)

```
src/
├── TailwindMerge.php           Entry point: merge(), tw(), join(), withConfig()
└── Lib/
    ├── DefaultConfig.php       All Tailwind v3 class groups and conflict maps
    ├── ClassGroupUtils.php     Trie builder + group resolver (getClassGroupId)
    ├── MergeClassList.php      Core merge algorithm (right-to-left scan)
    ├── ParseClassName.php      Splits a class token into modifiers/base/postfix
    ├── Validators.php          Predicate functions used by DefaultConfig
    └── LruCache.php            O(1) LRU cache for memoising merge results

```

### How it works

[](#how-it-works)

**1. Parse** — `ParseClassName::parseClassName()` splits each class token into:

- `modifiers` — variant prefixes like `['hover', 'focus', 'md']`
- `hasImportantModifier` — whether `!` is present
- `baseClassName` — the bare utility name e.g. `bg-red-500/50`
- `maybePostfixModifierPosition` — position of `/` in the base (for opacity variants)
- `hasPrefix` — whether the configured Tailwind prefix was found and stripped

**2. Look up group** — `ClassGroupUtils::getClassGroupId()` walks a trie built from `DefaultConfig` to find the group ID for the base class. Three strategies in order:

1. *Negative gate* — strips leading `-`, looks up the trie, only returns a group if it's in the `NEGATIVE_VALUE_GROUPS` allowlist (e.g. `-m-4` → group `m`, but `-p-4` → `null`)
2. *Trie lookup* — descends segment by segment; on each node tries literal key matches first, then registered validator functions
3. *Arbitrary property fallback* — detects `[font-size:1rem]` or `[--var:value]` syntax and returns a synthetic group ID based on the property name

**3. Conflict tracking** — `MergeClassList::merge()` iterates **right-to-left** (later = higher priority). For each class it builds a conflict key:

```
{!?}{sorted-variants:}{groupId}

```

e.g. `'bg-color'`, `'!p'`, `'focus:hover:text-color'`. If the key is already in the seen-set, the class is dropped. Otherwise the key AND all related group keys are marked as seen.

**4. Cache** — the raw input string is used as an LRU cache key. Identical calls are returned immediately without re-parsing.

### Trie node structure

[](#trie-node-structure)

```
map['bg']['__validators__'] = [isArbitraryPosition, isArbitrarySize, isArbitraryImage, isAny]
map['bg']['red']['500']['__group__'] = 'bg-color'
map['bg']['fixed']['__group__'] = 'bg-attachment'
map['grid']['cols']['none']['__group__'] = 'grid-cols'
map['grid']['cols']['__validators__'] = [isInteger, isArbitraryNumber]

```

Validators are checked in registration order. More specific validators (e.g. `isArbitraryLength`) are registered before the catch-all `isAny` to prevent false matches.

### CSS variable disambiguation

[](#css-variable-disambiguation)

Tailwind v4's `(--var)` syntax for CSS variables requires careful routing. The rule:

- **Labelled validators** (`isArbitraryVariablePosition`, `isArbitraryVariableLength`, etc.) only match when the value explicitly carries the `label:` prefix: `(position:center)`, `(length:--my-len)`.
- **Bare CSS variables** like `(--brand)` skip all labelled validators and fall through to the unlabelled `isAny` catch-all in the colour group.

This ensures `bg-(--brand)` is correctly attributed to `bg-color` rather than `bg-position` or `bg-size`.

---

Running tests
-------------

[](#running-tests)

```
composer install
vendor/bin/phpunit
```

Test coverage includes:

FileTestsWhat it covers`TailwindMergeTest.php`181End-to-end merge scenarios for all utility categories`PrefixTest.php`48v4 variant-style and v3 dash-style prefix handling`ParseClassNameTest.php`17Class token parsing, arbitrary variants, postfix, sortModifiers`ValidatorsTest.php`13Every validator function including label-disambiguation`LruCacheTest.php`9LRU eviction, sentinel, empty-string caching---

Differences from the JS package
-------------------------------

[](#differences-from-the-js-package)

FeatureJS (tailwind-merge)PHP portTailwind v3 class groups✓ (v2.x)✓Tailwind v4 prefix style✓✓ (`prefix: 'tw'`)Tailwind v3 prefix style✓ (v2.x)✓ (`prefix: 'tw-'`)Arbitrary values `[value]`✓✓Arbitrary variables `(--var)`✓✓Arbitrary properties `[prop:val]`✓✓Negative values (`-m-4`)✓✓Opacity postfix (`bg-red/50`)✓✓`twMerge`✓`TailwindMerge::tw()``twJoin`✓`TailwindMerge::join()``extendTailwindMerge`✓`TailwindMerge::withConfig(['extend' => …])``mergeConfigs`✓`TailwindMerge::mergeConfigs()` public static`getDefaultConfig`✓`TailwindMerge::getDefaultConfig()``fromTheme()`✓Not planed---

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance87

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

97d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4924096?v=4)[piplup](/maintainers/piplup)[@piplup](https://github.com/piplup)

---

Top Contributors

[![sadik-malik](https://avatars.githubusercontent.com/u/38027249?v=4)](https://github.com/sadik-malik "sadik-malik (9 commits)")

---

Tags

phpcsstailwindmergetailwindcss

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/piplup-tailwind-merge-php/health.svg)

```
[![Health](https://phpackages.com/badges/piplup-tailwind-merge-php/health.svg)](https://phpackages.com/packages/piplup-tailwind-merge-php)
```

###  Alternatives

[yieldstudio/tailwind-merge-php

Merge Tailwind CSS classes without style conflicts

4975.4k1](/packages/yieldstudio-tailwind-merge-php)[gehrisandro/tailwind-merge-laravel

TailwindMerge for Laravel merges multiple Tailwind CSS classes by automatically resolving conflicts between them

341876.3k31](/packages/gehrisandro-tailwind-merge-laravel)[gehrisandro/tailwind-merge-php

TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them

1391.8M12](/packages/gehrisandro-tailwind-merge-php)[tales-from-a-dev/tailwind-merge-php

TailwindMerge for PHP merges multiple Tailwind CSS classes by automatically resolving conflicts between them

12388.1k10](/packages/tales-from-a-dev-tailwind-merge-php)[tailwindphp/tailwindphp

A full port of TailwindCSS 4.x to PHP

193.0k4](/packages/tailwindphp-tailwindphp)[arnolem/tailwindphp

TailwindPHP - use Tailwind CSS in your PHP projects (without npm)

1427.5k](/packages/arnolem-tailwindphp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
