autoprefixer
Autoprefixer is a PostCSS plugin that reads your CSS, looks up each property, value, selector, and at-rule against the Can I Use database, and writes the vendor-prefixed variants your target browsers still need. You write `::placeholder` and it emits `::-moz-placeholder` alongside it. It also works in reverse: prefixes that no browser in your target list needs any more get deleted, so a stylesheet copied from a 2015 blog post gets cleaned up rather than bloated. Which browsers count is not an autoprefixer setting; it reads your Browserslist config, the same list Babel and ESLint use, from package.json or a .browserslistrc file.
Still the default prefixing step for any PostCSS-based build, and the removal of dead prefixes is worth as much as the additions. If you are starting fresh in 2026 with a Rust-based toolchain, Lightning CSS does the same job in the bundler you already run.
Use it if
- You already run PostCSS in your build (Tailwind v3, Next.js, Vite with a postcss.config, webpack with postcss-loader) and want prefixing handled without thinking about it
- Your Browserslist target includes older Safari or Samsung Internet, where properties like backdrop-filter, mask, and text-size-adjust genuinely still need -webkit-
- You inherited a stylesheet full of hand-written prefixes and want the outdated ones removed automatically instead of auditing them by hand
- You want prefixing driven by the same Browserslist config as your JS toolchain, so bumping the target updates CSS and JS output together
- Your build already prefixes CSS: Lightning CSS (used by Vite when you set css.transformer, and inside Tailwind v4) and esbuild both add prefixes from a browser target, so a second PostCSS pass is dead weight
- Your Browserslist target is recent evergreen browsers only: flexbox, transforms, transitions, and border-radius have not needed prefixes for years, so the plugin's output is close to a no-op and mostly costs build time
- You expect it to make features work in old browsers: it only writes prefixes, so it will not polyfill Grid, container queries, :has(), or anything else that a browser has simply not implemented
- You need IE 10 and 11 Grid support: it is off by default, and the grid: 'autoplace' mode has documented limitations around autoplacement, nested grids, and grid-gap in some cases, so you will still be hand-writing -ms- rules
- You cannot maintain caniuse-lite: the prefix data is baked into a dependency that goes stale, and every project using Browserslist eventually starts printing an outdated-database warning until someone runs the update command
Setup reality
npm install -D autoprefixer postcss, then add it to a postcss.config.js as `plugins: [require('autoprefixer')]`. It is a PostCSS plugin, not a standalone tool, so postcss ^8.1.0 is a peer dependency you must install yourself; npm will install it automatically but pnpm and yarn PnP will not always, and you get a cryptic plugin error instead. The second half of setup is Browserslist: with no config it silently uses the defaults query, which is not the same as your team's support policy, so put a browserslist key in package.json or a .browserslistrc file and share it with Babel. Prefix data comes from caniuse-lite, a pinned dependency, so a lockfile that has not been touched in a year prefixes for last year's browsers and CI starts printing 'Browserslist: browsers data is several months old'; the fix is `npx update-browserslist-db@latest`, not upgrading autoprefixer. Grid prefixes for IE are off by default and only turn on through the grid option, the AUTOPREFIXER_GRID environment variable, or a control comment. Finally, order matters in the plugin list: put autoprefixer after anything that generates CSS and before minification.
Patterns
Add autoprefixer to a PostCSS configpostcss-config
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
};Vite, Next.js, Parcel, and postcss-loader all pick this file up automatically from the project root. Put autoprefixer after plugins that generate CSS (nesting, Tailwind) and before cssnano, or you prefix code that does not exist yet.
Tell it which browsers to targetbrowserslist-config
// package.json
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
# or .browserslistrc
> 0.5%
last 2 versions
not deadAutoprefixer has no browser list of its own. With no config it falls back to the Browserslist defaults query, which is almost certainly not your support policy. Keeping it in package.json means Babel and ESLint read the same targets.
See which prefixes will actually be addedcheck-what-it-does
npx autoprefixer --info
# Browsers:
# Safari: 16
# Properties:
# backdrop-filter: webkit
# text-size-adjust: webkitRun this before arguing about whether the plugin earns its place. On a modern target the properties list is often five lines long, which is the honest answer to whether you still need it.
Fix the outdated caniuse-lite warningupdate-prefix-data
npx update-browserslist-db@latest
git add package-lock.json
git commit -m 'chore: refresh browserslist db'The warning 'Browserslist: browsers data (caniuse-lite) is X months old' means your prefix decisions are stale, not that autoprefixer is out of date. Upgrading autoprefixer itself usually does nothing; this command updates the pinned data in the lockfile.
Run it directly from Nodejavascript-api
const autoprefixer = require('autoprefixer');
const postcss = require('postcss');
const result = await postcss([autoprefixer])
.process(css, { from: 'src/app.css', to: 'dist/app.css' });
result.warnings().forEach(warn => console.warn(warn.toString()));
console.log(result.css);Always pass from and to or PostCSS warns about missing source maps. Reuse one processor across many files instead of building a new one per file; the README calls this out as a real speed difference.
Turn individual behaviours on and offplugin-options
require('autoprefixer')({
cascade: false, // do not indent prefixed props to line up
remove: false, // keep outdated prefixes someone wrote on purpose
flexbox: 'no-2009', // skip the 2009 display: box syntax
supports: false // leave @supports conditions alone
})remove: false is the option people actually need: by default autoprefixer deletes prefixes no target browser requires, which will strip a deliberate hack for a device you do not have in Browserslist.
Emit -ms- Grid prefixes for IE 10 and 11ie-grid-prefixes
require('autoprefixer')({ grid: 'autoplace' })
/* or per block, in the CSS itself */
/* autoprefixer grid: autoplace */
.layout { display: grid; grid-template-columns: 1fr 1fr; }Grid translation is off by default because it is partial. autoplace supports simple autoplacement but the README documents real limits: no support for some gap cases, nested grids need explicit placement, and enabling it on an old project can change existing layouts.
Disable prefixing for one rule or blockcontrol-comments
.a {
transition: 1s; /* prefixed normally */
}
.b {
/* autoprefixer: off */
transition: 1s; /* left alone */
}
.c {
/* autoprefixer: ignore next */
transition: 1s; /* left alone */
mask: url(i.png); /* still prefixed */
}'autoprefixer: off' applies to the whole block, both before and after the comment, which surprises people who expect it to act like a line directive. Do not put an off and an on comment in the same block.
Override targets for one build onlyoverride-browserslist
require('autoprefixer')({
overrideBrowserslist: ['ie >= 11', 'last 2 Safari versions']
})The README asks you not to use this: it desynchronizes CSS targets from the Browserslist config Babel reads. Legitimate uses are a separate legacy bundle or a CSS file shipped to a known embedded browser, not general configuration.
Strip outdated prefixes without adding anyremove-only-mode
require('autoprefixer')({ add: false })Useful as a one-off codemod on a stylesheet full of 2014 prefixes: run it, commit the diff, then switch back to the default. It only deletes prefixes no browser in your target list needs, so the result is still safe for your users.
Prefix files from the command linecli-usage
npm install --save-dev postcss postcss-cli autoprefixer
npx postcss src/*.css --use autoprefixer -d dist/The autoprefixer binary itself only prints info; running it over files goes through postcss-cli. Handy for a CSS file that is not part of a bundler build, such as an email template or a static docs theme.
Surface the warnings it emits about your CSSread-warnings
const result = await postcss([autoprefixer]).process(css, { from: undefined });
for (const warn of result.warnings()) {
console.warn(warn.toString());
}
// e.g. "Gradient has outdated direction syntax"Autoprefixer only warns about things it cannot fix for you, mainly old gradient direction syntax and the 2009 display: box flexbox. Most build integrations print these already, but a hand-rolled PostCSS script swallows them unless you ask.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| lightningcss | npm | You want prefixing, minification, and modern-syntax lowering in one Rust pass instead of a PostCSS pipeline, and you can accept a different plugin story. |
| postcss-preset-env | npm | You want autoprefixer plus polyfills for newer CSS syntax (nesting, custom media, colour functions) picked by the same Browserslist target; it bundles autoprefixer inside. |
| esbuild | npm | Your CSS goes through esbuild already and setting a target there gives you enough prefixing without adding PostCSS at all. |