gehrisandro/tailwind-merge-laravel
Merge Tailwind CSS classes in Laravel and automatically resolve conflicts (later classes win). Ideal for Blade components and directives. PHP/Laravel port of tailwind-merge. Supports Tailwind v3.0–v3.3 (Laravel 10+).
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require gehrisandro/tailwind-merge-laravel
Publish the config (optional but recommended for customization):
php artisan vendor:publish --provider="TailwindMerge\Laravel\TailwindMergeServiceProvider"
First Use Case:
In a Blade component, replace $attributes->class with $attributes->twMerge() to merge classes dynamically:
<!-- components/Button.blade.php -->
<button {{ $attributes->twMerge('px-4 py-2 rounded bg-blue-500 hover:bg-blue-600') }}>
{{ $slot }}
</button>
Usage in a view:
<x-button class="text-white font-bold">Click Me</x-button>
Output:
<button class="px-4 py-2 rounded bg-blue-500 hover:bg-blue-600 text-white font-bold">Click Me</button>
Key Files to Review:
config/tailwind-merge.php: Customize prefixes, class groups, or disable the Blade directive.app/View/Components/ or your Blade component directory: Start integrating twMerge into existing components.$attributes->class with $attributes->twMerge('base-classes') in component definitions.
<div {{ $attributes->twMerge('p-4 border rounded-lg') }}>
{{ $slot }}
</div>
twMergeFor() for nested elements (e.g., icons, badges) with withoutTwMergeClasses() to avoid attribute pollution:
<button {{ $attributes->withoutTwMergeClasses()->twMerge('px-4 py-2') }}>
<svg {{ $attributes->twMergeFor('icon', 'h-5 w-5') }} ...></svg>
{{ $slot }}
</button>
Usage:
<x-button class="bg-red-500" class:icon="text-white">Delete</x-button>
@twMerge() for ad-hoc merging in views:
<div @twMerge('text-lg', 'text-red-500', 'font-bold')>Dynamic Text</div>
use TailwindMerge\Laravel\Facades\TailwindMerge;
$mergedClasses = TailwindMerge::merge('bg-gray-100', 'hover:bg-gray-200');
Or via helper:
$mergedClasses = twMerge('bg-gray-100', 'hover:bg-gray-200');
public $buttonClasses = 'px-4 py-2 rounded';
public $activeButtonClasses = 'bg-green-500 text-white';
Blade:
<button class="{{ twMerge($buttonClasses, $active ? $activeButtonClasses : '') }}">
{{ $slot }}
</button>
x-bind:class with merged classes:
<div x-data="{ isOpen: false }"
:class="twMerge('p-4', isOpen ? 'bg-blue-100' : 'bg-gray-100')">
<!-- ... -->
</div>
<div class="{{ twMerge('bg-white', 'dark:bg-gray-800', 'text-black', 'dark:text-white') }}">
Content
</div>
$tenantClasses = Tenant::find($id)->theme_classes;
$merged = twMerge('bg-primary', $tenantClasses);
classGroups in config/tailwind-merge.php for custom utilities:
'classGroups' => [
'custom' => [
['bg' => ['brand-primary', 'brand-secondary']],
['text' => ['brand-text']],
],
],
<div class="{{ twMerge('bg-brand-primary', 'hover:bg-brand-secondary') }}">...</div>
Attribute Pollution:
$attributes->merge() instead of twMerge will duplicate classes.twMerge or twMergeFor in components.withoutTwMergeClasses():
{{ $attributes->withoutTwMergeClasses()->twMerge('...') }}
Non-Tailwind Classes:
class="custom-class") may not merge predictably.<div class="{{ twMerge('custom-class', 'p-4') }}">...</div>
Caching Quirks:
@twMerge) are not cached by default.Tailwind Version Mismatch:
Nested twMergeFor:
twMergeFor calls can lead to attribute name collisions.<div {{ $attributes->twMergeFor('wrapper', 'p-4') }}>
<button {{ $attributes->twMergeFor('button', 'px-2') }}>...</button>
</div>
Usage:
<x-component class:wrapper="bg-gray-100" class:button="bg-blue-500">...</x-component>
Inspect Merged Output:
Use dd(twMerge('class1', 'class2')) to debug conflicts in Tinker or a route.
Validate Tailwind Config:
If merging fails, verify your tailwind.config.js aligns with the package’s assumptions (e.g., no conflicting custom classes).
Check for Typos:
Arbitrary values (e.g., z-[999]) must match Tailwind’s syntax exactly.
Disable Directive Temporarily:
Set 'blade_directive' => null in config/tailwind-merge.php to isolate issues.
Cache Merged Classes: Cache results in Livewire/Alpine or controllers for dynamic components:
// Livewire component
public $mergedClasses;
public function mount() {
$this->mergedClasses = twMerge('base', 'dynamic');
}
Avoid Over-Merging: Merge only necessary classes in loops or large datasets to reduce overhead.
Custom Merge Logic:
Extend the underlying TailwindMerge\Laravel\TailwindMerge class to add pre/post-processing:
// app/Providers/AppServiceProvider.php
use TailwindMerge\Laravel\TailwindMerge;
public function boot() {
TailwindMerge::macro('customMerge', function ($classes) {
return TailwindMerge::merge($classes, '!important-class');
});
}
Add New Class Groups:
Extend config/tailwind-merge.php for project-specific utilities:
'classGroups' => [
'app' => [
['btn' => ['btn-primary', 'btn-secondary']],
['spacing' => ['gap-1', 'gap-2']],
],
],
Integrate with View Composers:
Pre-merge classes globally in AppServiceProvider:
public function boot() {
view()->composer('*', function ($view) {
$view->with('mergedClasses', twMerge('global-base', 'global-variant'));
});
}
| Scenario | Solution | |-----------------------------------
How can I help you explore Laravel packages today?