CSS preprocessors are popular, but they have some drawbacks. In this introduction to PostCSS, we’ll explore the advantages of PostCSS, how it works, and what its extensive range of plugins can achieve.
The Value and Limitations of Preprocessors
Most CSS developers are familiar with preprocessors. Tools including Sass, Less, and Stylus introduced concepts such as file partials, nesting, variables, and mixins. Some features are gradually appearing in native CSS, but a preprocessor is still useful for managing large codebases and maintaining style and coding consistency.
It may be difficult to imagine life without a CSS preprocessor, but there are downsides:
-
Preprocessors are not extendable or limitable. Most preprocessors are a black box which provide you a specific set of supported features. It may be possible to write custom functions, but functionality beyond the scope of that tool is remains impossible — such as inlining an SVG as a background image.
Similarly, you can’t stop developers using options you’d rather avoid such as
@extend
or deep nesting. Linting can help, but it won’t stop the preprocessor compiling a valid file. -
Preprocessors provide their own syntax. Preprocessor code may resemble CSS, but no browser can parse the file natively. The syntax is different and, if your tool changes or is unavailable, your code will require updates to make it usable.
The benefits more than outweigh these risks, but there is an alternative …
What is PostCSS?
PostCSS is not a preprocessor (although it can behave like one). It’s a Node.js tool which takes valid CSS and enhances it. Even those using Sass, Less, or Stylus often run a PostCSS step after the initial CSS compilation. You may have encountered the PostCSS Autoprefixer plugin which automatically prepends -webkit
, -moz
, and -ms
vendor prefixes to CSS properties which require them.
On its own, PostCSS does nothing. It’s a parser which tokenizes CSS code to create an abstract syntax tree. A plugin can process this tree and update properties accordingly. Once all plugins have completed their work, PostCSS reformats everything back into a string and outputs to a CSS file.
Around 350 plugins are available, and most perform a single task such as inlining @import
declarations, simplifying calc()
functions, handling image assets, syntax linting, minifying, and more. A more user friendly plugin search is available at the PostCSS plugins catalogue.
PostCSS benefits include:
-
You start with standard CSS. PostCSS is to CSS what Babel is to JavaScript. It can take a standard stylesheet which works in recent browsers and output CSS which works everywhere — for example, transpiling the newer
inset
property back intotop
,bottom
,left
, andright
properties. Over time, you could drop this process as more browsers supportinset
.Admittedly, some plugins allow you to parse preprocessor-like syntax which isn’t standard CSS, but you don’t have to use them.
-
Use the plugins and features you need. PostCSS is configurable, and you can adopt the plugins you require. For example, you could support partials and nesting but not permit variables, loops, mixins, maps, and other features available in Sass.
-
Provide a custom configuration for every project. A individual project configuration can enhance or reduce the set of plugins used elsewhere. The options are far more varied than any preprocessor.
-
Write your own PostCSS plugins. A wide range of plugins is available for extending syntax, parsing future properties, adding fallbacks, optimizing code, processing colors, images, fonts, and even writing CSS in other languages such as Spanish and Russian.
In the unlikely event you can’t find what you need, you can write your own PostCSS plugin in JavaScript.
-
You’re possibly using PostCSS already. You may be able to remove your preprocessor dependencies if you’re already running a PostCSS plugin such as AutoPrefixer. PostCSS is not necessarily faster or more lightweight than using a preprocessor, but it can handle all CSS processing in a single step.
Installing PostCSS
PostCSS requires Node.js, but this tutorial demonstrates how to install and run PostCSS from any folder — even those that aren’t Node.js projects. You can also use PostCSS from webpack, Parcel, Gulp.js, and other tools, but we’ll stick to the command line.
Install PostCSS globally on your system by running the following:
npm install -g postcss-cli
Ensure it’s working by entering this:
postcss --help
Installing Your First PostCSS Plugin
You’ll require at least one plugin to do anything practical. The PostCSS import plugin is a good option which inlines all @import
declarations and merges your CSS into a single file. Install it globally like so:
npm install -g postcss-import
To test this plugin, open or create a new project folder such as cssproject
, then create a src
subfolder for your source files. Create a main.css
file to load all partials:
/* src/main.css */
@import '_reset';
@import '_elements';
Then create a _reset.css
file in the same folder:
/* src/reset.css */
* {
padding: 0;
margin: 0;
}
Follow this with an _elements.css
file:
/* src/elements.css */
body {
font-family: sans-serif;
}
label {
user-select: none;
}
Run PostCSS from the project’s root folder by passing the input CSS file, a list of plugins to --use
, and an --output
filename:
postcss ./src/main.css --use postcss-import --output ./styles.css
If you don’t have any errors, the following code will be output to a new styles.css
file in the project root:
/* src/main.css */
/* src/reset.css */
* {
padding: 0;
margin: 0;
}
/* src/elements.css */
body {
font-family: sans-serif;
}
label {
user-select: none;
}
/* sourceMappingURL=data:application/json;base64,...
Note that PostCSS can output CSS files anywhere, but the output folder must exist; it will not create the folder structure for you.
Enabling and Disabling Source Maps
An inline source map is output by default. When the compiled CSS file is used in an HTML page, examining it in the browser’s developer tools will show the original src
file and line. For example, viewing <body>
styles will highlight src/_elements.css
line 2 rather than styles.css
line 8.
You can create an external source map by adding a --map
(or -m
) switch to the postcss
command. There’s little benefit other than the CSS file is cleaner and the browser doesn’t need to load the source map unless the developer tools are open.
You can remove the source map with --no-map
. Always use this option when outputting CSS files for production deployment.
Install and Use the AutoPrefixer Plugin
The Autoprefixer plugin is often a developer’s first encounter with PostCSS. It adds vendor prefixes according to browser usage and rules defined at caniuse.com. Vendor prefixes are less used in modern browsers which hide experimental functionality behind flags. However, there are still properties such as user-select
which require -webkit-
, -moz-
, and -ms-
prefixes.
Install the plugin globally with this:
npm install -g autoprefixer
Then reference it as another --use
option on your postcss
command:
postcss ./src/main.css --use postcss-import --use autoprefixer --output ./styles.css
Examine the label
declaration from line 11 of styles.css
to view the vendor-prefixed properties:
label {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
AutoPrefixer uses the browserlist module to determine which browser prefixes to add. The default is:
> 0.5%
: a browser with at least 0.5% market sharelast 2 versions
: the last two releases of those browsersFirefox ESR
: including Firefox Extended Support Releasesnot dead
: any browser that is not discontinued
You can change these defaults by creating a .browserslistrc
file. For example:
> 2%
Or you can add a "browserslist"
array to package.json
in a Node.js project. For example:
"browserslist": [
"> 2%"
]
Targeting browsers with a 2% or more market share only requires a -webkit-
prefix in Safari:
label {
-webkit-user-select: none;
user-select: none;
}
Minify CSS with cssnano
cssnano minifies CSS by stripping whitespace, comments, and other unnecessary characters. Results will vary, but you can expect a 30% file reduction which you can deploy to production servers for better web page performance.
Install cssnano globally:
npm install -g cssnano
Then add it to your postcss
command. We’ll also include --no-map
to disable the source map:
postcss ./src/main.css --use postcss-import --use autoprefixer --use cssnano --no-map --output ./styles.css
This reduces the CSS file to 97 characters:
*{margin:0;padding:0}body{font-family:sans-serif}label{-webkit-user-select:none;user-select:none}
Automatically Build when Source Files Change
The PostCSS --watch
option automatically builds your CSS file when any of the source files change. You may also want to add the --verbose
switch which reports when a build occurs:
postcss ./src/main.css --use postcss-import --use autoprefixer --use cssnano --no-map --output ./styles.css --watch --verbose
Your terminal will show Waiting for file changes
. Make a change to any file and styles.css
is rebuilt. PostCSS will also report any problems such as syntax errors.
To finish, press Ctrl | Cmd + C in the terminal.
Create a PostCSS Configuration File
The postcss
command will become long and cumbersome as you add further plugins and options. You can create a JavaScript configuration file which defines all the options and can logically determine whether it’s running in a development or production environment.
Create a configuration file named postcss.config.cjs
in the root of your project folder. Aso note the following:
- you can put the file in another folder, but you’ll need to specify
--config <dir>
when runningpostcss
- you can use
postcss.config.js
as the file name, but PostCSS may fail in Node.js projects which have"type": "module"
set inpackage.json
Add the following code to postcss.config.cjs
:
// PostCSS configruation
module.exports = (cfg) => {
const devMode = (cfg.env === 'development');
return {
map: devMode ? 'inline' : null,
plugins: [
require('postcss-import')(),
require('autoprefixer')(),
devMode ? null : require('cssnano')()
]
};
};
PostCSS passes a cfg
object which contains the command line options. For example:
{
cwd: '/home/yourname/cssproject',
env: undefined,
options: {
map: { inline: true },
parser: undefined,
syntax: undefined,
stringifier: undefined
},
file: {
dirname: '/home/yourname/cssproject/src',
basename: 'main.css',
extname: '.css'
}
}
The module must return an object with optional properties:
map
: the source map settingparser
: whether to use a non-CSS syntax parser (such as the scss plugin)plugins
: an array of plugins and configurations to process in the order specified
The code above detects whether the postcss
command has an --env
option. This is a shortcut for setting the NODE_ENV
environment variable. To compile CSS in development mode, run postcss
with --env development
and, optionally, set --watch --verbose
. This creates an inline source map and does not minify the output:
postcss ./src/main.css --output ./styles.css --env development --watch --verbose
To run in production mode and compile minified CSS without a source map, use this:
postcss ./src/main.css --output ./styles.css
Ideally, you could run these as terminal or npm
scripts to reducing typing effort further.
PostCSS Progress
You now know the basics of PostCSS. Enhancing the functionality is a matter of adding and configuring further plugins. Invest some time and you’ll soon have a workflow you can adapt for any web project.
The tutorial on how to use PostCSS as a configurable alternative to Sass provides more configuration examples and plugin options.
Further links:
- PostCSS home page
- PostCSS CLI
- a plugins list for PostCSS
- a searchable catalog of plugins for PostCSS
- plugins on npmjs.com for PostCSS
- Writing a plugin for PostCSS