tailwindphp / tailwindphp
A full port of TailwindCSS 4.x to PHP
Requires
- php: ^8.2
Requires (Dev)
- brianium/paratest: ^7.4
- laravel/pint: ^1.26
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.0
README
🐘 TailwindPHP
A 1:1 port of TailwindCSS 4.x to PHP
Generate Tailwind CSS using pure PHP — no Node.js required.
Built with AI — This entire codebase (57,000+ lines, 4,000+ tests) was generated using Claude Code. No manual coding was done.
Why This Exists
TailwindCSS is written in TypeScript and requires Node.js. TailwindPHP is a complete rewrite in PHP, giving you the same functionality without the Node.js dependency.
- WordPress & PHP frameworks — Ship Tailwind-powered plugins and themes without requiring users to install Node.js or run build tools. Just
composer requireand go. See TailwindWP for a boilerplate WordPress block editor integration. - Dynamic CSS — Generate utility CSS at runtime based on user input, database content, or template variables.
- Simpler deployment — No Node.js in your production stack, no build step in CI, no npm in your Docker image.
Similar to how scssphp brought SCSS compilation to PHP, TailwindPHP brings the full Tailwind 4.x feature set to any PHP environment.
Batteries Included
Building TailwindPHP created an opportunity to unify the Tailwind ecosystem's best tools into a single package. The JavaScript world relies on several companion libraries that are essential to any serious Tailwind project — so we ported them too:
- clsx — Conditionally construct class strings (
cn('btn', isActive && 'btn-active')) - tailwind-merge — Intelligently merge classes without conflicts (
merge('p-2', 'p-4')→'p-4') - CVA — Create component variants declaratively (
variants(['size' => ['sm', 'md', 'lg']]))
Table of Contents
- Why This Exists
- Scope
- Status
- Installation
- CLI
- Usage
- API
- Classname Utilities
- Variants (CVA Port)
- Plugin System
- How It Works
- Testing
- Development
- License
- Credits
Scope
TailwindPHP provides full CSS compilation with support for both inline CSS and file-based imports.
// Inline CSS with Tailwind directives $css = tw::generate('<div class="bg-brand p-4">', '@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // File-based imports $css = tw::generate([ 'content' => '<div class="bg-brand p-4">', 'importPaths' => '/path/to/styles.css', ]);
What's included:
- All CSS compilation features (utilities, variants, directives, functions)
- File-based
@importresolution viaimportPathsoption - Virtual modules (
tailwindcss,tailwindcss/preflight,tailwindcss/utilities,tw-animate-css, etc.) - Preflight CSS reset
- Plugin system with
@tailwindcss/typography,@tailwindcss/forms, and custom plugin support cn(),variants(),merge(),join()— class name utilities (no separate packages needed)- No external dependencies beyond PHP
What's NOT included:
- IDE tooling — No IntelliSense, autocomplete, or source maps (these are editor features, not CSS compilation)
Status
✅ 4,002 tests passing — Feature complete for core TailwindCSS functionality plus utility libraries.
| Test Suite | Tests | Status |
|---|---|---|
| Core (utilities, variants, integration) | 1,322 | ✅ |
| API Coverage (utilities, modifiers, variants, directives, plugins) | 1,774 | ✅ |
| API (tw::generate, tw::compile, tw::properties, tw::computedProperties, etc.) | 140 | ✅ |
| PHP-specific unit tests (theme, design-system, utils, helpers) | 300 | ✅ |
| Import functionality | 42 | ✅ |
| Edge cases | 57 | ✅ |
| CSS Minifier | 17 | ✅ |
| Plugin system (typography, forms) | 25 | ✅ |
| clsx (from reference test suite) | 27 | ✅ |
| tailwind-merge (from reference test suite) | 52 | ✅ |
| CVA (from reference test suite) | 50 | ✅ |
| tw-animate-css | 23 | ✅ |
| Cache | 14 | ✅ |
| CLI | 26 | ✅ |
Performance
While this is a 1:1 port focused on correctness and maintainability, PHP-specific optimizations are applied where possible:
- toCss: Uses array accumulation + implode instead of string concatenation, pre-computed indent strings
- CSS Parser: Direct character comparison instead of ord() calls, tracked buffer lengths instead of strlen()
These optimizations maintain identical output while improving performance. TypeScript remains faster due to V8's JIT compilation, but this is expected for build-time CSS generation.
See benchmarks/ for detailed comparison.
Installation
composer require tailwindphp/tailwindphp
CLI
TailwindPHP includes a command-line interface that is a 1:1 port of @tailwindcss/cli - same options, same behavior, no Node.js required.
Quick Start
# Build CSS from an input file ./vendor/bin/tailwindphp -i ./src/app.css -o ./dist/styles.css # Watch for changes ./vendor/bin/tailwindphp -i ./src/app.css -o ./dist/styles.css --watch # Build minified ./vendor/bin/tailwindphp -i ./src/app.css -o ./dist/styles.css --minify
Options
tailwindphp [--input input.css] [--output output.css] [--watch] [options] Options: -i, --input Input CSS file (default: @import "tailwindcss") -o, --output Output file (default: stdout) -w, --watch Watch for changes and rebuild as needed -m, --minify Optimize and minify the output --optimize Optimize the output without minifying --cwd The current working directory (default: .) -h, --help Display usage information
Input CSS
Create an app.css file with your Tailwind imports and @source directive:
@import "tailwindcss"; @source "./templates"; /* Directory to scan for classes */
The @source directive tells TailwindPHP where to find your template files. It supports:
- Directories:
@source "./templates"; - Glob patterns:
@source "./src/**/*.php"; - Multiple sources: Add multiple
@sourcedirectives
Examples
# Build from CSS with @source directive tailwindphp -i ./src/app.css -o ./dist/styles.css # Build minified for production tailwindphp -i ./src/app.css -o ./dist/styles.css -m # Watch mode with minification tailwindphp -i ./src/app.css -o ./dist/styles.css -w -m # Use a different working directory tailwindphp -i app.css -o dist/styles.css --cwd=/path/to/project
Global Installation
Install globally to use tailwindphp from anywhere:
composer global require tailwindphp/tailwindphp
# Now available globally
tailwindphp -i ./src/app.css -o ./dist/styles.css
Usage
Basic Usage
The simplest way to use TailwindPHP is with the generate() function:
use TailwindPHP\tw; // Generate CSS from HTML containing Tailwind classes $css = tw::generate('<div class="flex items-center p-4 bg-blue-500">Hello</div>');
This parses the HTML, extracts class names, and generates only the CSS needed.
Configuration Options
You can pass configuration as either a second parameter or an array:
// Option 1: String as second parameter $css = tw::generate($html, '@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // Option 2: Array with 'content' and 'css' keys $css = tw::generate([ 'content' => '<div class="flex p-4 bg-brand">Hello</div>', 'css' => ' @import "tailwindcss"; @theme { --color-brand: #3b82f6; --font-heading: "Inter", sans-serif; } .btn { @apply px-4 py-2 rounded-lg bg-brand text-white; } ' ]);
Importing CSS Files
Use importPaths to load CSS from the filesystem with full @import resolution:
// Single file $css = tw::generate([ 'content' => '<div class="flex btn">Hello</div>', 'importPaths' => '/path/to/styles.css', ]); // Directory (loads all .css files alphabetically) $css = tw::generate([ 'content' => '<div class="flex btn">Hello</div>', 'importPaths' => '/path/to/css/', ]); // Multiple paths $css = tw::generate([ 'content' => '<div class="flex btn card">Hello</div>', 'importPaths' => [ '/path/to/base.css', '/path/to/components/', ], ]); // Combine with inline CSS $css = tw::generate([ 'content' => '<div class="flex btn custom">Hello</div>', 'css' => '.custom { color: red; }', 'importPaths' => '/path/to/styles.css', ]);
Nested imports are resolved automatically. If your CSS file contains @import "./buttons.css", TailwindPHP resolves the path relative to the importing file.
/* /path/to/styles.css */ @import "tailwindcss"; @import "./components/buttons.css"; @import "./components/cards.css"; @theme { --color-brand: #3b82f6; }
Deduplication: Multiple imports of the same file or virtual module (like @import "tailwindcss") are automatically deduplicated.
Custom resolver: For advanced use cases (virtual file systems, databases), provide a callable:
$css = tw::generate([ 'content' => '<div class="flex virtual-class">Hello</div>', 'importPaths' => function (?string $uri, ?string $fromFile): ?string { if ($uri === null) { return '@import "tailwindcss"; @import "custom.css";'; } if ($uri === 'custom.css') { return '.virtual-class { color: purple; }'; } return null; // Unknown imports are silently skipped }, ]);
Preflight
Preflight is Tailwind's opinionated set of base styles, built on top of modern-normalize. It smooths over cross-browser inconsistencies and makes it easier to work within the constraints of the design system.
For full details on what Preflight does and why, see the official Tailwind Preflight documentation.
Key resets include:
- Default margins removed — Headings, paragraphs, lists, etc. have zero margin
- Headings unstyled — All headings have the same font-size and font-weight as normal text
- Lists unstyled —
ulandolhave no bullets/numbers or padding - Images are block-level — No more phantom space below images
- Border styles reset — Empty borders so you can add borders just by setting
border-width - Buttons inherit fonts — Buttons use the parent's font family, size, and line-height
- Hidden elements stay hidden — Elements with a
hiddenattribute are invisible unless usinghidden="until-found"
// Full import (includes theme + preflight + utilities) $css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'css' => '@import "tailwindcss";', ]);
Extending Preflight
Add your own base styles on top of Preflight using @layer base:
$css = tw::generate([ 'content' => '<article><h1>Title</h1><p>Content</p></article>', 'css' => ' @import "tailwindcss"; @layer base { h1 { font-size: var(--text-2xl); } h2 { font-size: var(--text-xl); } a { color: var(--color-blue-600); text-decoration-line: underline; } } ', ]);
Disabling Preflight
To disable Preflight — for example when integrating into an existing project or defining your own base styles — import only the parts of Tailwind you need.
By default, @import "tailwindcss" is equivalent to:
@layer theme, base, components, utilities; @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css" layer(utilities);
To skip Preflight, simply omit its import:
$css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'css' => ' @layer theme, base, components, utilities; @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/utilities.css" layer(utilities); ', ]);
Import Modifiers
When importing Tailwind's files individually, modifiers like prefix(), theme(), and important should go on their respective imports:
/* Prefix affects both theme variables and utilities */ @import "tailwindcss/theme.css" layer(theme) prefix(tw); @import "tailwindcss/utilities.css" layer(utilities) prefix(tw); /* important affects utilities */ @import "tailwindcss/utilities.css" layer(utilities) important; /* theme(static) or theme(inline) affects theme variables */ @import "tailwindcss/theme.css" layer(theme) theme(static);
Extract Class Names
If you need to extract Tailwind class names from content separately:
use TailwindPHP\tw; $classes = tw::extractCandidates('<div class="flex p-4" className="bg-blue-500">'); // ['flex', 'p-4', 'bg-blue-500']
Minification
Minify CSS output for production:
use TailwindPHP\tw; // Option 1: Minify during generation $css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'minify' => true, ]); // Option 2: Minify separately $css = tw::generate('<div class="flex p-4">Hello</div>'); $minified = tw::minify($css);
The minifier removes comments, collapses whitespace, shortens hex colors (#ffffff to #fff), removes units from zero values (0px to 0), and other optimizations.
Caching
Enable file-based caching to avoid recompiling identical content:
use TailwindPHP\tw; // Cache to default directory (sys_get_temp_dir()/tailwindphp) $css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'cache' => true, ]); // Cache to custom directory $css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'cache' => '/path/to/cache', ]); // With TTL (time-to-live in seconds) $css = tw::generate([ 'content' => '<div class="flex p-4">Hello</div>', 'cache' => true, 'cacheTtl' => 3600, // Expire after 1 hour ]);
The cache key is computed from the content, CSS configuration, and minify option. Different inputs generate different cache files.
Clear the cache:
use TailwindPHP\tw; use function TailwindPHP\clearCache; // Clear default cache tw::clearCache(); // Clear custom cache directory tw::clearCache('/path/to/cache'); // Or use the function clearCache('/path/to/cache');
API
TailwindPHP provides a comprehensive API for CSS generation and inspection.
tw::generate()
Generate CSS from HTML content containing Tailwind classes.
Returns: string - The generated CSS
use TailwindPHP\tw; // String input $css = tw::generate('<div class="flex p-4">'); // String with custom CSS $css = tw::generate('<div class="flex bg-brand">', '@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // Array input $css = tw::generate([ 'content' => '<div class="flex p-4">', 'css' => '@import "tailwindcss";', 'minify' => true, ]);
tw::compile()
Create a reusable TailwindCompiler instance for multiple operations.
Returns: TailwindCompiler - A reusable compiler instance
use TailwindPHP\tw; // Create compiler with default Tailwind $compiler = tw::compile(); // Create compiler with custom CSS $compiler = tw::compile('@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // Generate CSS $css = $compiler->generate('<div class="flex p-4 bg-brand">'); // Or minify the output $minified = $compiler->minify($css);
tw::properties()
Get raw CSS properties for a class (unresolved CSS variables).
Returns: array<string, string> - Property name to value mapping
use TailwindPHP\tw; // Single class tw::properties('p-4'); // ['padding' => 'calc(var(--spacing) * 4)'] // Multiple classes (pass as array) tw::properties(['flex', 'items-center', 'p-4']); // ['display' => 'flex', 'align-items' => 'center', 'padding' => 'calc(var(--spacing) * 4)'] // From compiler instance $compiler = tw::compile(); $compiler->properties('p-4');
tw::computedProperties()
Get computed CSS properties with all variables resolved.
Returns: array<string, string> - Property name to resolved value mapping
use TailwindPHP\tw; // Static method tw::computedProperties('p-4'); // ['padding' => '1rem'] tw::computedProperties('text-blue-500'); // ['color' => 'oklch(.546 .245 262.881)'] // Multiple classes (pass as array) tw::computedProperties(['flex', 'items-center', 'gap-4']); // ['display' => 'flex', 'align-items' => 'center', 'gap' => '1rem'] // From compiler instance $compiler = tw::compile(); $compiler->computedProperties('p-4'); // ['padding' => '1rem']
tw::value()
Get raw value for a utility class (unresolved).
Returns: ?string - The raw CSS value, or null if utility not found
use TailwindPHP\tw; // Static method tw::value('p-4'); // 'calc(var(--spacing) * 4)' tw::value('text-blue-500'); // 'var(--color-blue-500)' // From compiler instance $compiler = tw::compile(); $compiler->value('p-4');
tw::computedValue()
Get computed value for a utility class (resolved).
Returns: ?string - The resolved CSS value, or null if utility not found
use TailwindPHP\tw; // Static method tw::computedValue('p-4'); // '1rem' tw::computedValue('text-blue-500'); // 'oklch(.546 .245 262.881)' tw::computedValue('gap-4'); // '1rem' // From compiler instance $compiler = tw::compile(); $compiler->computedValue('p-4'); // '1rem'
tw::extractCandidates()
Extract Tailwind class names from content.
Returns: array<string> - Array of extracted class names
use TailwindPHP\tw; tw::extractCandidates('<div class="flex p-4" className="bg-blue-500">'); // ['flex', 'p-4', 'bg-blue-500'] // From compiler instance $compiler = tw::compile(); $compiler->extractCandidates('<div class="flex p-4">'); // ['flex', 'p-4']
tw::minify()
Minify CSS output.
Returns: string - The minified CSS
use TailwindPHP\tw; $css = tw::generate('<div class="flex p-4">'); $minified = tw::minify($css); // From compiler instance $compiler = tw::compile(); $css = $compiler->generate('<div class="flex p-4">'); $minified = $compiler->minify($css);
tw::clearCache()
Clear the CSS cache.
Returns: int - Number of files deleted
use TailwindPHP\tw; // Clear default cache tw::clearCache(); // Clear custom cache directory tw::clearCache('/path/to/cache');
tw::colors()
Get all color values from the theme.
Returns: array<string, string> - Map of color name to computed value
use TailwindPHP\tw; tw::colors(); // ['red-500' => 'oklch(63.7% 0.237 25.331)', 'blue-500' => 'oklch(62.3% 0.214 259.815)', ...] // With custom theme tw::colors('@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // [..., 'brand' => '#3b82f6'] // From compiler instance $compiler = tw::compile(); $compiler->colors();
tw::breakpoints()
Get all breakpoint values from the theme.
Returns: array<string, string> - Map of breakpoint name to value
use TailwindPHP\tw; tw::breakpoints(); // ['sm' => '40rem', 'md' => '48rem', 'lg' => '64rem', 'xl' => '80rem', '2xl' => '96rem'] // With custom theme tw::breakpoints('@import "tailwindcss"; @theme { --breakpoint-xs: 20rem; }'); // ['xs' => '20rem', 'sm' => '40rem', ...] // From compiler instance $compiler = tw::compile(); $compiler->breakpoints();
tw::spacing()
Get custom spacing values from the theme.
Returns: array<string, string> - Map of spacing name to value
use TailwindPHP\tw; // Note: TailwindCSS 4 uses a single --spacing base value, not --spacing-* namespace // This returns any custom --spacing-* values defined in the theme tw::spacing('@import "tailwindcss"; @theme { --spacing-huge: 10rem; }'); // ['huge' => '10rem'] // From compiler instance $compiler = tw::compile(); $compiler->spacing();
Input Formats
All static methods accept multiple input formats:
// Format 1: String only tw::generate('<div class="flex">'); tw::properties('p-4'); // Format 2: String + CSS string tw::generate('<div class="flex">', '@import "tailwindcss"; @theme { ... }'); tw::properties('bg-brand', '@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // Format 3: Array with 'content' and optional 'css' tw::generate(['content' => '<div class="flex">', 'css' => '@import "tailwindcss";']); tw::properties(['content' => 'p-4', 'css' => '@import "tailwindcss";']);
TailwindCompiler Instance Methods
When using tw::compile(), the returned compiler provides instance methods:
$compiler = tw::compile('@import "tailwindcss"; @theme { --color-brand: #3b82f6; }'); // Generate CSS $css = $compiler->generate('<div class="flex p-4 bg-brand">'); // Extract candidates $candidates = $compiler->extractCandidates('<div class="flex p-4 bg-brand">'); // Get properties $compiler->properties('bg-brand'); // ['background-color' => 'var(--color-brand)'] $compiler->computedProperties('bg-brand'); // ['background-color' => '#3b82f6'] // Get single values $compiler->value('bg-brand'); // 'var(--color-brand)' $compiler->computedValue('bg-brand'); // '#3b82f6' // Minify CSS $minified = $compiler->minify($css); // Get theme values $compiler->colors(); // All color values $compiler->breakpoints(); // All breakpoint values $compiler->spacing(); // Custom spacing values // Access internals (advanced) $compiler->getTheme(); // Theme object with resolved values $compiler->getDesignSystem(); // Design system with utilities, variants, etc. $compiler->getCompiled(); // Raw compiled state array
Classname Utilities
TailwindPHP includes PHP ports of the popular Tailwind companion libraries. No additional packages required.
cn()
The recommended utility. Combines conditional class construction with intelligent conflict resolution.
use function TailwindPHP\cn; // Basic usage - conflicts are resolved cn('px-2 py-1', 'px-4'); // => 'py-1 px-4' (px-4 overrides px-2) // Conditional classes cn('btn', ['btn-primary' => true, 'btn-disabled' => false]); // => 'btn btn-primary' // React-style component 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']);
merge()
Merge Tailwind classes, resolving conflicts. Later classes override earlier ones.
use function TailwindPHP\merge; merge('px-2 py-1 bg-red-500', 'px-4 bg-blue-500'); // => 'py-1 px-4 bg-blue-500' merge('hover:bg-red-500', 'hover:bg-blue-500'); // => 'hover:bg-blue-500'
join()
Join classes without conflict resolution. Use when you know there are no conflicts.
use function TailwindPHP\join; join('foo', 'bar', null, 'baz'); // => 'foo bar baz'
Variants (CVA Port)
PHP port of CVA (Class Variance Authority) for creating component style variants.
variants()
Create component variants with declarative configuration.
use function TailwindPHP\variants; // Define component styles $button = variants([ 'base' => 'inline-flex items-center justify-center rounded-md font-medium', 'variants' => [ 'variant' => [ 'default' => 'bg-primary text-white hover:bg-primary/90', 'outline' => 'border border-input bg-background hover:bg-accent', 'ghost' => 'hover:bg-accent hover:text-accent-foreground', ], 'size' => [ 'default' => 'h-10 px-4 py-2', 'sm' => 'h-9 px-3', 'lg' => 'h-11 px-8', ], ], 'defaultVariants' => [ 'variant' => 'default', 'size' => 'default', ], ]); // Usage (React-style single props object) $button(); // defaults applied $button(['variant' => 'outline']); // override variant $button(['size' => 'sm', 'class' => 'mt-4']); // override + custom class // Use in a component function with cn() for easy class extension function Button(array $props = []): string { static $styles = null; $styles ??= variants([ 'base' => 'inline-flex items-center justify-center rounded-md font-medium', 'variants' => [ 'variant' => [ 'default' => 'bg-primary text-white hover:bg-primary/90', 'outline' => 'border border-input hover:bg-accent', ], 'size' => [ 'default' => 'h-10 px-4 py-2', 'sm' => 'h-9 px-3', ], ], 'defaultVariants' => ['variant' => 'default', 'size' => 'default'], ]); // cn() merges variant output with custom classes, resolving conflicts $class = cn($styles($props), $props['class'] ?? null); $text = $props['children'] ?? 'Button'; return "<button class=\"{$class}\">{$text}</button>"; } // Custom classes override variant defaults via cn() Button(['variant' => 'outline', 'size' => 'sm', 'class' => 'mt-4 px-8']);
compose()
Merge multiple variant components into one.
use function TailwindPHP\variants; use function TailwindPHP\compose; $box = variants(['variants' => ['shadow' => ['sm' => 'shadow-sm', 'md' => 'shadow-md']]]); $stack = variants(['variants' => ['gap' => ['1' => 'gap-1', '2' => 'gap-2']]]); $card = compose($box, $stack); $card(['shadow' => 'md', 'gap' => '2']); // => 'shadow-md gap-2'
Plugin System
TailwindPHP includes PHP ports of official TailwindCSS plugins. These are 1:1 ports following the same logic as the JavaScript originals.
Built-in Plugins
| Plugin | Description |
|---|---|
@tailwindcss/typography |
Beautiful typographic defaults for HTML content |
@tailwindcss/forms |
Form element reset and styling utilities |
Usage
Use the @plugin directive in your CSS:
$css = tw::generate([ 'content' => '<article class="prose prose-lg"><h1>Hello</h1><p>Content here</p></article>', 'css' => ' @plugin "@tailwindcss/typography"; @import "tailwindcss/utilities.css"; ' ]);
Plugin Options
Pass options using CSS block syntax:
// Typography with custom class name $css = ' @plugin "@tailwindcss/typography" { className: "markdown"; } @import "tailwindcss/utilities.css"; '; // Forms with class strategy (no base styles) $css = ' @plugin "@tailwindcss/forms" { strategy: "class"; } @import "tailwindcss/utilities.css"; ';
Typography Plugin
Generates the .prose class with beautiful typographic defaults:
// Basic usage $css = tw::generate('<div class="prose">...</div>', '@plugin "@tailwindcss/typography"; @import "tailwindcss/utilities.css";'); // Available classes: prose, prose-sm, prose-lg, prose-xl, prose-2xl // Modifiers: prose-invert (dark mode), prose-slate, prose-gray, etc.
Forms Plugin
Provides form element utilities:
// Class strategy - explicit form classes $css = tw::generate( '<input class="form-input" /><select class="form-select">...</select>', '@plugin "@tailwindcss/forms" { strategy: "class"; } @import "tailwindcss/utilities.css";' ); // Available classes: form-input, form-textarea, form-select, form-multiselect, // form-checkbox, form-radio
Creating Custom Plugins
You can create your own plugins by implementing the PluginInterface:
use TailwindPHP\Plugin\PluginInterface; use TailwindPHP\Plugin\PluginAPI; class MyCustomPlugin implements PluginInterface { public function getName(): string { return 'my-custom-plugin'; } public function __invoke(PluginAPI $api, array $options = []): void { // Add static utilities $api->addUtilities([ '.btn' => [ 'padding' => '0.5rem 1rem', 'border-radius' => '0.25rem', 'font-weight' => '600', ], '.btn-primary' => [ 'background-color' => 'blue', 'color' => 'white', ], ]); // Add functional utilities with values $api->matchUtilities( [ 'tab' => function ($value) { return ['tab-size' => $value]; }, ], ['values' => ['1' => '1', '2' => '2', '4' => '4', '8' => '8']] ); // Add component classes $api->addComponents([ '.card' => [ 'background-color' => 'white', 'border-radius' => '0.5rem', 'padding' => '1rem', 'box-shadow' => '0 1px 3px rgba(0,0,0,0.1)', ], ]); // Add custom variants $api->addVariant('hocus', '&:hover, &:focus'); // Access theme values $primary = $api->theme('colors.blue.500', '#3b82f6'); } public function getThemeExtensions(array $options = []): array { return []; // Return theme additions if needed } }
Register and use your plugin:
use TailwindPHP\tw; use function TailwindPHP\registerPlugin; // Register the plugin registerPlugin(new MyCustomPlugin()); // Use it in CSS $css = tw::generate( '<div class="btn btn-primary card tab-4">...</div>', '@plugin "my-custom-plugin"; @import "tailwindcss/utilities.css";' );
Architecture
The plugin system follows the TailwindCSS plugin API pattern:
src/plugin.php # PluginInterface, PluginAPI, PluginManager
src/plugin/plugins/
├── typography-plugin.php # @tailwindcss/typography port
└── forms-plugin.php # @tailwindcss/forms port
PluginAPI provides the same methods as TailwindCSS:
addBase(array $css)— Add base stylesaddUtilities(array $utilities)— Add static utilitiesmatchUtilities(array $utilities, array $options)— Add functional utilities with valuesaddComponents(array $components)— Add component classesaddVariant(string $name, string|array $variant)— Add custom variantsmatchVariant(string $name, callable $callback, array $options)— Add functional variantstheme(string $path, mixed $default)— Access theme valuesconfig(string $path, mixed $default)— Access config values
How It Works
Architecture
The codebase mirrors TailwindCSS's structure — same file names, same organization:
src/
├── _tailwindphp/ # PHP-specific helpers (NOT part of the TailwindCSS port)
│ ├── LightningCss.php # CSS optimizations (lightningcss Rust library equivalent)
│ ├── CssMinifier.php # CSS minification
│ └── lib/ # Companion library ports
│ ├── clsx/ # clsx port (27 tests from reference)
│ ├── tailwind-merge/ # tailwind-merge port (52 tests from reference)
│ └── cva/ # CVA port (50 tests from reference)
│
├── plugin/ # Plugin system
│ └── plugins/ # Built-in plugin implementations
│ ├── typography-plugin.php # @tailwindcss/typography port
│ └── forms-plugin.php # @tailwindcss/forms port
│
├── utilities/ # Utility implementations (split from utilities.ts)
│ ├── accessibility.php # sr-only, forced-colors
│ ├── backgrounds.php # bg-*, gradient-*, from-*, via-*, to-*
│ ├── borders.php # border-*, rounded-*, divide-*, outline-*
│ ├── effects.php # shadow-*, opacity-*, mix-blend-*
│ ├── filters.php # blur-*, brightness-*, contrast-*, etc.
│ ├── flexbox.php # flex-*, grid-*, gap-*, justify-*, align-*
│ ├── interactivity.php # cursor-*, scroll-*, touch-*, select-*
│ ├── layout.php # display, position, z-*, overflow-*, etc.
│ ├── masks.php # mask-linear-*, mask-radial-*, mask-conic-*, mask-x/y/t/r/b/l-*
│ ├── sizing.php # w-*, h-*, min-*, max-*, size-*
│ ├── spacing.php # m-*, p-*, space-*
│ ├── svg.php # fill-*, stroke-*
│ ├── tables.php # border-collapse, table-layout
│ ├── transforms.php # translate-*, rotate-*, scale-*, skew-*
│ ├── transitions.php # transition-*, duration-*, ease-*, delay-*
│ └── typography.php # font-*, text-*, leading-*, tracking-*, text-shadow-*
│
├── utils/ # Helper functions (ported from utils/)
│
├── index.php # Main entry point, compile(), cn(), variants(), merge(), join()
├── ast.php # AST nodes and toCss()
├── candidate.php # Candidate parsing (class name → parts)
├── compile.php # Candidate to CSS compilation
├── css-functions.php # theme(), --theme(), --spacing(), --alpha()
├── css-parser.php # CSS parsing
├── design-system.php # Central registry for utilities/variants
├── plugin.php # Plugin system (PluginInterface, PluginAPI, PluginManager)
├── theme.php # Theme value resolution
├── utilities.php # Utility registration and lookup
├── value-parser.php # CSS value parsing
├── variants.php # Variant handling (hover, focus, responsive, etc.)
└── walk.php # AST traversal
Note: TailwindCSS's utilities.ts is 6,000+ lines. We split it into src/utilities/ (one file per category) for maintainability.
Port Deviation Markers
All implementation files are documented with @port-deviation markers explaining where and why the PHP implementation differs from TypeScript:
| Marker | Meaning |
|---|---|
@port-deviation:none |
Direct 1:1 port with no deviations |
@port-deviation:async |
PHP uses synchronous code (no async/await) |
@port-deviation:storage |
Different data structures (array vs Map/Set) |
@port-deviation:types |
PHPDoc instead of TypeScript types |
@port-deviation:sourcemaps |
Source map tracking omitted |
@port-deviation:omitted |
Entire module/feature not ported (outside scope) |
@port-deviation:errors |
Different error handling approach |
@port-deviation:enum |
PHP constants instead of TypeScript enums |
@port-deviation:dispatch |
Different function dispatch pattern |
@port-deviation:structure |
Different code organization |
@port-deviation:helper |
PHP-specific helper not in original |
Testing
Tests ensure the PHP port stays in sync with TailwindCSS's TypeScript implementation. We use two approaches:
Test Types
-
Extraction-based tests — Tests automatically extracted from TailwindCSS's
.test.tsfiles using scripts intest-coverage/. These cover complex utilities, variants, and integration tests. -
Unit test ports — Direct PHP ports of simpler TypeScript test files (AST, parsing, escaping, etc.). These live alongside their source files as
*.test.php.
Running Tests
# Extract tests from TypeScript source composer extract # Run all tests composer test # Run specific test file ./vendor/bin/phpunit src/utilities.test.php # Run tests matching a pattern ./vendor/bin/phpunit --filter="translate" # Run library tests only ./vendor/bin/phpunit src/_tailwindphp/lib/
How Extraction Works
- Extract — Scripts in
test-coverage/parse TailwindCSS's.test.tsfiles and extract test cases to JSON - Run — PHPUnit tests read extracted data and compare PHP output against expected CSS
- Verify — Any mismatch means the PHP port has drifted from TailwindCSS behavior
Test Coverage
| Category | Tests | Source |
|---|---|---|
| Extraction-based | ||
| utilities.test.php | 547 | utilities.test.ts |
| variants.test.php | 139 | variants.test.ts |
| index.test.php | 78 | index.test.ts |
| css_functions.test.php | 60 | css-functions.test.ts |
| ui_spec.test.php | 68 | ui.spec.ts |
| Unit test ports | ||
| css_parser.test.php | 70 | css-parser.test.ts |
| candidate.test.php | 66 | candidate.test.ts |
| decode_arbitrary_value.test.php | 60 | decode-arbitrary-value.test.ts |
| ast.test.php | 18 | ast.test.ts |
| escape.test.php | 10 | escape.test.ts |
| + 12 more unit test files | ~180 | Various .test.ts files |
| Library tests | ||
| clsx.test.php | 27 | clsx/test/*.js |
| tailwind_merge.test.php | 52 | tailwind-merge/tests/*.ts |
| cva.test.php | 50 | cva/src/index.test.ts |
| API coverage tests | 1,684 | Custom exhaustive tests |
| Plugin tests | 25 | Plugin functionality |
Requirements
- PHP 8.2+
- Composer
Development
See CLAUDE.md for detailed development guide, project structure, and porting phases.
License
MIT
Credits
This project ports:
- TailwindCSS by Tailwind Labs
- clsx by Luke Edwards
- tailwind-merge by Dany Castillo
- CVA by Joe Bell