CSS Browser Prefixes (Vendor Prefixes)
CSS vendor prefixes are special property or value prefixes — like -webkit-, -moz-, -ms-, and -o- — that browser makers historically added in front of experimental CSS features before those features became stable, standardized parts of the language. They let browsers ship new functionality early without accidentally clashing with a slightly different future standard. Today most of the CSS you write needs no prefix at all, but a handful of newer or historically inconsistent features — like text-selection styling, backdrop blur effects, and gradient text clipping — still require one or more prefixed versions to work reliably across every browser your visitors might use.
This lesson explains where prefixes came from, exactly how the browser’s CSS parser handles them, which ones are still relevant in 2026, and how to add them correctly (and sparingly) so your stylesheets stay both compatible and maintainable.
Overview / How Prefixes Work
When a browser engine implements a brand-new CSS feature that hasn’t been finalized by the CSS Working Group yet, it ships that feature under a prefixed name so that authors who use it understand it’s experimental and subject to change. The major prefixes map to browser engines, not to specific browsers:
| Prefix | Engine / Vendor | Browsers |
|---|---|---|
-webkit- |
WebKit / Blink | Safari, Chrome, Edge (Chromium), Opera, most mobile browsers |
-moz- |
Gecko | Firefox |
-ms- |
Trident/EdgeHTML | Internet Explorer, legacy (pre-Chromium) Edge |
-o- |
Presto | Old Opera (pre-Blink, before 2013) |
Structurally, a vendor prefix is just part of the property name or value keyword — the CSS parser treats -webkit-transform and transform as two completely unrelated property names. The parser doesn’t understand that they’re “related”; it simply reads whichever declarations it recognizes and silently ignores (not errors on) any property or value it doesn’t understand. That silent-ignore behavior is exactly what makes prefixes safe to ship: a browser that doesn’t know -moz-appearance just skips that line and moves on to the next declaration, rather than breaking the whole rule.
Because a browser engine can rename or drop a prefixed feature at any time (it’s explicitly unstable), and because -webkit- in particular became so dominant that other engines started supporting some -webkit- prefixed properties themselves just for compatibility, the web platform has been moving away from vendor prefixes since roughly 2015 in favor of feature flags and faster standardization. Most CSS you’ll write today — flexbox, grid, custom properties, clamp() — needs zero prefixes. Prefixes now mostly linger around a small set of newer or historically messy features.
Syntax
The general shape is: write the prefixed version(s) of a declaration first, followed by the standard, unprefixed version last, inside the same rule.
selector {
-webkit-property: value;
-moz-property: value;
-ms-property: value;
property: value;
}
- Prefixed declarations come first — they act as a fallback for browsers that don’t yet support the unprefixed form.
- The unprefixed (standard) declaration comes last — in the cascade, when two declarations for the same property appear in the same rule, the one written later wins in a browser that understands both. Since prefixed and unprefixed names are technically different properties, this isn’t a specificity override — it’s about giving the modern browser the final say by having the real, spec-compliant property present.
- You only need to include the prefixes for engines that actually require that specific feature — not all four, every time.
- Prefixes apply to values too, not just property names — for example
display: -webkit-box;orbackground: -moz-linear-gradient(...)in old code.
Examples
Example 1: Disabling text selection with a vendor-prefixed property
.no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
Result: Text inside any element with class no-select can no longer be highlighted or selected by click-and-drag in any supported browser, including older WebKit/Blink and Gecko builds that only recognized the prefixed form.
user-select is unprefixed and fully supported in all current evergreen browsers, but it spent years as a prefixed-only feature, so you’ll still see all four lines in many real-world stylesheets and CSS resets for maximum backward compatibility, especially on sites that support slightly older mobile WebViews.
Example 2: Gradient text with -webkit-background-clip
.gradient-heading {
background: linear-gradient(90deg, #6366f1, #ec4899);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
Result: The heading’s text characters are filled with a horizontal indigo-to-pink gradient instead of a solid color — the gradient is visible only inside the shapes of the letters themselves, with everything outside the glyphs remaining transparent (showing the page background through it).
This is one of the clearest cases where a prefix is still required in practice: background-clip: text is standardized, but Safari has historically only honored it when -webkit-background-clip: text is also present, and -webkit-text-fill-color has no standard equivalent replacement in some engines, so both the prefixed and unprefixed color-transparency declarations are kept together.
Example 3: A frosted-glass card with backdrop-filter
.glass-card {
background: rgba(255, 255, 255, 0.15);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 12px;
padding: 24px;
}
Result: The card renders with a semi-transparent white tint over whatever sits behind it, and that background is visibly blurred (a “frosted glass” effect), with a thin translucent border and rounded corners framing the box.
backdrop-filter is standardized and supported by modern Chrome, Firefox, and Edge without a prefix, but Safari (and some WebKit-based mobile browsers) still requires the -webkit-backdrop-filter form to activate the blur, so both lines are needed for the effect to render consistently everywhere.
How It Works Step by Step
- The CSS parser reads your stylesheet top to bottom, one declaration at a time, inside each rule block.
- For every declaration, it checks whether it recognizes the property name (including any prefix) as one this browser engine implements.
- If the property is unrecognized — for example a
-moz-property in Chrome — the parser discards just that one declaration and continues to the next; it does not throw an error or invalidate the rest of the rule. - If multiple recognized declarations set what is functionally the same visual behavior (like
-webkit-user-selectanduser-selectboth being understood by a modern WebKit browser), the one that appears later in source order takes effect, because they are literally different property names being applied in sequence — this is why the unprefixed line always goes last. - The browser then proceeds with layout and paint using only the declarations it kept, exactly as if the unsupported prefixed lines had never been written.
Common Mistakes
Mistake 1: Putting the unprefixed property before the prefixed one
.box {
transform: rotate(10deg);
-webkit-transform: rotate(10deg);
}
This is backwards. In a browser that supports both lines, the last declaration wins — so here the -webkit-transform line, listed second, would override the standard transform line even though they set the same value in this example. The danger shows up when the values later drift out of sync during edits: someone updates transform but forgets the prefixed copy below it, and the stale prefixed value silently wins in WebKit browsers. Always put prefixed declarations first and the standard property last:
.box {
-webkit-transform: rotate(10deg);
transform: rotate(10deg);
}
Mistake 2: Shipping only a prefixed property with no standard fallback
.panel {
-webkit-appearance: none;
}
This only removes native form-control styling in WebKit/Blink browsers. Firefox and other engines that support the standardized appearance property (without needing a prefix) never receive any instruction at all, so the panel keeps its default browser chrome there. Always pair a prefix with its standard counterpart, even if the standard version is newer or less universally supported yet:
.panel {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
Best Practices
- Don’t guess — check current browser support (e.g. on caniuse.com) before adding a prefix; many properties you might assume still need one, like
border-radiusorflex, have not needed prefixes for years. - Use a build tool like Autoprefixer (commonly run via PostCSS) in real projects instead of hand-writing prefixes — it reads your target browser list and inserts only the prefixes those browsers actually need, and removes stale ones automatically.
- Always list prefixed declarations before the unprefixed standard declaration, never after.
- Never ship a prefixed-only declaration; always include the standard property so future and non-WebKit browsers get the behavior too.
- Periodically audit old stylesheets for prefixes that are no longer needed — leftover prefixes from years ago add dead weight and make rules harder to read.
- Remember that prefixes apply per-feature, not per-browser-generally — a browser can require a prefix for one property while supporting a dozen other properties unprefixed.
Practice Exercises
Exercise 1: Write a rule for a class named .frosted that applies a background blur of 10px using backdrop-filter, making sure to include both the WebKit-prefixed and standard declarations in the correct order.
Exercise 2: The following rule has a bug related to prefix ordering — rewrite it correctly: a rule sets user-select: none; first, followed by -webkit-user-select: none; and -moz-user-select: none; afterward. Explain in one sentence why the original order is risky even though the values match.
Exercise 3: Create a gradient-text heading rule for a class called .brand-title using a linear gradient from orange to red, including every declaration needed (prefixed and standard) for the text-clipping effect to render in both Safari and Chrome.
Summary
- Vendor prefixes (
-webkit-,-moz-,-ms-,-o-) let browser engines ship experimental CSS features before they’re finalized in the spec. - The CSS parser silently ignores any property or value it doesn’t recognize, which is what makes prefixed fallbacks safe to include.
- Prefixed declarations should always come before the standard, unprefixed declaration in the same rule, so the modern browser’s own implementation takes precedence.
- Most modern CSS (flexbox, grid, custom properties) needs no prefixes at all; only a small set of features (like
backdrop-filterandbackground-clip: textin Safari) still commonly require one today. - In real projects, use a tool like Autoprefixer rather than hand-writing prefixes, to keep your CSS both correct and free of unnecessary bloat.
