padaliyajay/php-autoprefixer
A PHP port of Autoprefixer that adds vendor prefixes to your CSS without Node.js or PostCSS. Write modern properties and let it handle browser compatibility, with support for custom browser lists and working from CSS files or strings.
Install it like any other Composer dependency — there is no Node toolchain, no PostCSS config, and no binary to compile:
composer require padaliyajay/php-autoprefixer
Where to look first
vendor/padaliyajay/php-autoprefixer/README.md — the canonical usage example and the browser-targeting options for your pinned version.vendor/padaliyajay/php-autoprefixer/src/ — the Autoprefixer entry class plus the Compiler, Browser, Vendor, and Element\* handler classes. Read these before you assume a property is supported.src/ — this is what determines which prefixes get emitted. If a prefix is missing, check this data before you blame your CSS.The minimal call
use Padaliyajay\PHPAutoprefixer\Autoprefixer;
$css = '.card { display: flex; user-select: none; }';
$autoprefixer = new Autoprefixer($css);
$autoprefixer->compile();
echo (string) $autoprefixer; // .card{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;...}
Your first real use case in Laravel
The natural entry point is not a per-request middleware — it's a build/deploy step. Create a one-off check in tinker or a throwaway route to confirm the output for your actual stylesheet:
Route::get('/_prefix-check', function () {
return response(
(string) (new \Padaliyajay\PHPAutoprefixer\Autoprefixer(
file_get_contents(public_path('css/app.css'))
))->compile()
)->header('Content-Type', 'text/css');
});
If that emits -webkit- / -ms- variants for your flexbox and user-select rules, you're ready to wire it into an Artisan command (below). Remove the route immediately afterward.
1. Service container binding
Wrap the library so the rest of the app never touches it directly. This gives you one place to swap in PostCSS later and one place to control browser targets:
// app/Support/CssAutoprefixer.php
namespace App\Support;
use Padaliyajay\PHPAutoprefixer\Autoprefixer;
class CssAutoprefixer
{
public function __construct(private array $browsers = []) {}
public function prefix(string $css): string
{
$processor = new Autoprefixer($css);
// Apply targets before compiling — see the README for the exact
// setter name in your pinned version (set_browsers / setBrowsers).
if ($this->browsers) {
foreach ($this->browsers as $target) {
// e.g. 'chrome 50', 'ie 11', 'safari 9'
$processor->set_browser($target);
}
}
$processor->compile();
return (string) $processor;
}
}
// app/Providers/AppServiceProvider.php
$this->app->singleton(\App\Support\CssAutoprefixer::class, fn () => new \App\Support\CssAutoprefixer(
config('autoprefixer.browsers', [])
));
2. Artisan build command (the recommended default)
Treat autoprefixing as a deploy-time transformation of already-compiled CSS. Order matters: Sass/Less/Tailwind compile → autoprefix → minify → write to public/.
class PrefixCss extends Command
{
protected $signature = 'css:prefix {--in=public/css/app.css} {--out=public/css/app.prefixed.css}';
public function handle(CssAutoprefixer $prefixer)
{
$in = base_path($this->option('in'));
$out = base_path($this->option('out'));
$prefixed = $prefixer->prefix(file_get_contents($in));
file_put_contents($out, $prefixed);
$this->info("Wrote {$out}");
}
}
Then in your deploy script: php artisan css:prefix && php artisan css:minify. Do not chain this into composer install scripts unless you're comfortable with deploy-time filesystem writes.
3. Blade directive for inline/critical CSS
For above-the-fold CSS inlined into the layout, a directive keeps views tidy:
// AppServiceProvider::boot()
Blade::directive('autoprefix', function ($expression) {
return "<?php echo app(\App\Support\CssAutoprefixer::class)->prefix($expression); ?>";
});
<style>@autoprefix($criticalCss)</style>
Because this runs at render time, pair it with a cache (next pattern) or restrict it to small, static critical-CSS strings.
4. Mail templates
Emails are the strongest argument for a PHP autoprefixer: mail clients are notoriously inconsistent and your CSS is often generated or inlined by PHP, outside any Vite pipeline. A view composer on your mail layouts is a clean hook:
View::composer('emails.*', function ($view) {
$view->with('emailCss', app(CssAutoprefixer::class)->prefix(
file_get_contents(resource_path('css/email.css'))
));
});
5. Cache the compiled result
Never prefix the same stylesheet twice per request. Key on file mtime plus the browser config hash, and store in storage/framework/cache:
$key = 'css.prefixed.' . md5_file($path) . '.' . md5(json_encode(config('autoprefixer.browsers')));
$css = Cache::rememberForever($key, fn () => $prefixer->prefix(file_get_contents($path)));
6. Configuration file
// config/autoprefixer.php
return [
'browsers' => ['chrome 50', 'ie 11', 'safari 9', 'firefox 45'],
];
Keep this in version control so a CI run and a production deploy produce byte-identical CSS.
7. Testing
Golden-file tests are the only reliable way to catch regressions when you bump the package or the bundled browser data:
public function test_flexbox_gets_prefixed(): void
{
$out = app(CssAutoprefixer::class)->prefix('.a { display: flex; }');
$this->assertStringContainsString('-webkit-flex', $out);
$this->assertStringContainsString('display: flex', $out);
}
autoprefixer npm package parses CSS with PostCSS's full AST and has years of edge-case handling. This library is a pure-PHP approximation. Modern syntax — nesting, @layer, @container, complex :is()/:has() selectors, custom properties in shorthand positions — may be passed through unchanged, reordered, or (worst case) mangled. Run your entire stylesheet through it and diff before adopting.browserslist in package.json. Your .browserslistrc, tailwind.config.js targets, and Vite's defaults are ignored. If you need a prefix the data doesn't know about, you must update the package or contribute the data.compile() mutates; cast to string. compile() returns the object (or nothing) depending on version — always render via (string) $autoprefixer. Do not assume the constructor is enough.*.prefixed.css file so browser DevTools line numbers still correspond to something.matthiasmullie/minify or similar; the two compose cleanly since both are pure PHP.Element\Declaration handler for that property, then check the browser data entry. More often than not the property simply has no handler, or your target list excludes every browser that needs the prefix.Element\* handler classes are the intended seam for adding support for a property the library doesn't know. Subclass and register rather than patching vendor code — otherwise your next composer update erases the fix.composer.json, commit your golden-file fixtures, and keep a note in your README on how to replace it with PostCSS if the project ever stalls.How can I help you explore Laravel packages today?