Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tailwind Merge Php Laravel Package

gehrisandro/tailwind-merge-php

Merge Tailwind CSS class strings in PHP with automatic conflict resolution (later classes win). PHP port of dcastil/tailwind-merge, compatible with Tailwind v3.0–v3.4 and PHP 8.1+. Create instances and customize configuration as needed.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation: Add the package via Composer:
    composer require gehrisandro/tailwind-merge-php
    
  2. First Use: Import and merge classes in a Blade file or service:
    use TailwindMerge\TailwindMerge;
    
    $merged = TailwindMerge::instance()->merge('text-red-500 hover:text-blue-500', 'hover:text-green-500');
    // Output: 'text-red-500 hover:text-green-500'
    
  3. Blade Integration: Create a helper in app/Helpers/tailwind.php:
    if (!function_exists('twMerge')) {
        function twMerge(...$classes) {
            return TailwindMerge::instance()->merge(...$classes);
        }
    }
    
    Use in Blade:
    <div class="{{ twMerge('p-4', 'px-6') }}">...</div>
    

First Use Case: Dynamic Component Styling

Merge conflicting classes in a Livewire component or Blade partial:

// Livewire component
public function render()
{
    $baseClasses = 'bg-gray-100 hover:bg-gray-200';
    $dynamicClasses = 'hover:bg-blue-500';

    return view('livewire.example', [
        'classes' => TailwindMerge::instance()->merge($baseClasses, $dynamicClasses),
    ]);
}
<div class="{{ $classes }}">Hover me</div>

Implementation Patterns

1. Blade Directives for Reusable Merging

Create a custom Blade directive (app/Providers/BladeServiceProvider.php):

Blade::directive('merge', function ($expression) {
    return "<?php echo TailwindMerge::instance()->merge({$expression}); ?>";
});

Usage:

<div class="@merge(['p-4', 'px-6', 'lg:px-8'])">...</div>

2. Service Integration for API/Email Templates

Extend a service to handle dynamic class merging:

class EmailService {
    public function generateButtonClasses(array $modifiers): string
    {
        $base = 'px-4 py-2 rounded text-white';
        $dynamic = collect($modifiers)->map(fn($mod) => "bg-{$mod}-500 hover:bg-{$mod}-600")->implode(' ');
        return TailwindMerge::instance()->merge($base, $dynamic);
    }
}

3. Livewire Property Observers

Merge classes reactively in Livewire:

public $size = 'md';

public function updatedSize()
{
    $this->classes = TailwindMerge::instance()->merge(
        'p-4',
        "text-{$this->size}-font"
    );
}

4. Caching for Performance

Configure caching in a service container binding:

// config/app.php
'bindings' => [
    TailwindMerge::class => function ($app) {
        return TailwindMerge::factory()
            ->withCache($app->make(\Illuminate\Cache\CacheManager::class))
            ->make();
    },
],

Usage:

$cachedMerge = app(TailwindMerge::class)->merge(...);

5. Custom Configuration for Tailwind Extensions

Extend for custom Tailwind classes (e.g., tw- prefix):

$merge = TailwindMerge::factory()
    ->withConfiguration([
        'prefix' => 'tw-',
        'classGroups' => [
            'font-size' => [['text' => ['very-large']]],
        ],
    ])
    ->make();

6. Validation Layer for Dynamic Classes

Sanitize user-provided classes before merging:

public function mergeUserClasses(array $userClasses): string
{
    $validClasses = collect($userClasses)
        ->filter(fn($class) => preg_match('/^(?!.*\s*!.*\s*).*$/', $class)) // Reject !important
        ->values()
        ->toArray();

    return TailwindMerge::instance()->merge($validClasses);
}

Gotchas and Tips

Pitfalls

  1. Arbitrary Value Conflicts:

    • Merging z-10 and z-[999] works, but z-[10] and z-10 may not resolve as expected.
    • Fix: Use explicit arbitrary values (e.g., z-[1000]) for critical cases.
  2. Dark Mode State Overrides:

    • Later dark mode classes (e.g., dark:text-gray-700) override earlier ones (dark:text-white).
    • Tip: Structure classes to prioritize defaults first, then overrides:
      $merged = TailwindMerge::instance()->merge(
          'text-white dark:text-gray-500',
          'dark:text-gray-700'
      ); // 'text-white dark:text-gray-700'
      
  3. Caching Invalidation:

    • Clear cache when updating classGroups configuration:
      $merge->getCache()->clear();
      
  4. Non-Tailwind Classes:

    • Non-Tailwind classes (e.g., custom-class) are preserved but may cause unexpected behavior if they conflict with Tailwind’s internal logic.
    • Workaround: Prefix non-Tailwind classes (e.g., nc-custom) and exclude them from merging.
  5. PHP 8.1+ Requirement:

    • Uses named arguments and strict typing. Downgrading may break type safety.
  6. Performance with Large Inputs:

    • Merging hundreds of classes in a loop can be slow. Use caching or batch processing:
      $cacheKey = md5(serialize($classes));
      return $merge->getCache()->remember($cacheKey, 3600, fn() => $merge->merge($classes));
      

Debugging Tips

  1. Inspect Merged Classes:

    • Log intermediate steps to debug conflicts:
      $instance = TailwindMerge::factory()->withDebug(true)->make();
      $instance->merge(...); // Logs each step
      
  2. Validate Tailwind Config:

    • If merges behave unexpectedly, compare your tailwind.config.js with the supported defaults.
  3. Test Edge Cases:

    • Use the package tests as a reference for edge cases:
      // Test arbitrary values
      $merge->merge('z-[10]', 'z-[20]'); // 'z-[20]'
      // Test hover states
      $merge->merge('hover:bg-red-500', 'hover:bg-blue-500'); // 'hover:bg-blue-500'
      

Extension Points

  1. Custom Validators:

    • Extend TailwindMerge\Validators\ValidatorInterface to handle project-specific classes:
      class CustomValidator implements ValidatorInterface {
          public function validate(string $class): bool { ... }
      }
      
    • Register in the factory:
      $merge = TailwindMerge::factory()
          ->withValidators([new CustomValidator()])
          ->make();
      
  2. Plugin System:

    • Create a plugin interface to extend merging logic:
      interface MergePlugin {
          public function handle(array $classes): array;
      }
      
    • Example: Auto-add !important to specific classes:
      class ImportantPlugin implements MergePlugin {
          public function handle(array $classes): array {
              return array_map(fn($class) => str_ends_with($class, '-focus') ? "!{$class}" : $class, $classes);
          }
      }
      
  3. Laravel Service Provider:

    • Bind a global instance with app-specific config:
      public function register() {
          $this->app->singleton(TailwindMerge::class, function ($app) {
              return TailwindMerge::factory()
                  ->withConfiguration(config('tailwind.merge'))
                  ->withCache($app->make('cache'))
                  ->make();
          });
      }
      

Configuration Quirks

  1. Prefix Handling:

    • The prefix config (e.g., tw-) must match exactly. Partial matches (e.g., tw) won’t work.
  2. Class Group Priorities:

    • Later entries in classGroups override earlier ones. Structure groups logically:
      'classGroups' => [
          'spacing' => [['p', 'px', 'py']],
          'colors' => [['bg', 'text']], // Overrides spacing if conflicts exist
      ]
      
  3. Arbitrary Value Support:

    • Ensure your Tailwind config includes arbitrary value support:
      // tailwind.config.js
      module.exports = {
        corePlugins: {
          preflight: false,
          arbitraryValues: true
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4
php-http/client-implementation
phpcr/phpcr-implementation
cucumber/gherkin-monorepo
haydenpierce/class-finder
psr/simple-cache-implementation