Stop writing CSS vendor prefixes manually

Apr 29, 2021css-practicescss

Major browser vendors implement CSS features at their own speed, and they have been using vendor prefixes to add experimental or nonstandard CSS properties. Following are a list of major prefixes:

  • -webkit-: Chrome, Safari, WebKit based browsers
  • -ms-: Internet Explorer, Microsoft Edge
  • -o-: pre-WebKit versions of Opera
  • -moz-: Firefox
.example {
  -webkit-border-radius: 10px;
  -moz-border-radius: 10px;
  -ms-border-radius: 10px;
  -o-border-radius: 10px;
  border-radius: 10px;
}

In theory, these prefixed properties should not be used in production. But in reality it doesn’t work as expected, here’s why:

  • CSS is forgivable, unsupported properties will be ignored so it doesn’t hurt to put new features in production.
  • It takes forever to wait until those prefixed properties to be standardized, developers just ignore the experimental nature and eager to try but don’t care to remove stale ones.
  • CSS is not practically polyfilled, this is one way to support old browsers.
  • It’s better safe than sorry, some developers just recklessly prefix unnecessary properties.

Vendor prefixing is one way to extend browser support to the point those prefixed properties implemented but not before any of that.

Modern CSS has moved away from vendor prefixing in favor of feature queries where you can style conditionally and provide fallback styles for something unsupported.

If you’re still writing vendor prefixes then you should consider setup automatic prefixing because manual vendor prefixing doesn’t scale well. It’s painful, tedious and error-prone for human eyes to keep track of which properties need to be prefixed for which browser versions.

Feature queries

The CSS @supports at-rule lets you specify declarations that depend on a browser’s support for one or more specific CSS features, known as feature queries.

Feature queries associate a block of statements with a supports condition. The supports condition consists of one or more name-value pairs combined by conjunctions (and), disjunctions (or), and/or negations (not). Precedence of operators can be defined with parentheses.

@supports (display: grid) {
  /* */
}
@supports not (display: grid) {
  /* */
}
@supports (display: grid) and (not (display: inline-grid)) {
  /* */
}
@supports (-moz-transform-style: preserve) {
  /* */
}

This is the direction major browser vendors are pushing forward, it works great when providing a mean to write fallback styles when something not supported, but keep in mind following limitations:

  • The @support at-rule will only work on browsers that support @supports. Don’t forget to write fallback code outside of feature queries.
  • You still need to keep track which features you need to check manually, caniuse.com can help with that. Obviously it’s less mundane than writing prefixes and less worried about new browsers in market as long as they support feature queries.
  • Browsers that do not support CSS Grid, which also doesn’t support feature queries.

Automatic prefixing

Consider vendor prefixing is something can’t be removed completely even when using feature queries because of fears of missing out unchecked features. On the rise of CSS (pre|post)-processors, vendor prefixing can be done automatically. For this solution to work, you’ll need following tools:

  • A tool to define target browsers. Browserslist is the best you can find, it will find target browsers automatically when you specify requirements in .browserslistrc as follow:
# supported browsers
defaults
not IE 11
maintained node versions
  • A tool to prefix automatically based on above target browsers. Autoprefixer is the best of its kind, a PostCSS plugin uses target browsers from browserslist and add prefixes based on caniuse.com data. Here’s an example setup with Gulp:
gulp.task('autoprefixer', () => {
  const autoprefixer = require('autoprefixer')
  const sourcemaps = require('gulp-sourcemaps')
  const postcss = require('gulp-postcss')

  return gulp
    .src('./src/*.css')
    .pipe(sourcemaps.init())
    .pipe(postcss([autoprefixer()]))
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest('./dest'))
})

Beside auto prefixing, PostCSS can help with polyfills but very limited when new CSS features require JavaScript implementation in client side.