While trying to find a solution for handling CSS in upcoming Baasic starter kits, we came up with a simple and interesting solution. Since we didn’t want to impose a particular preprocessor technology such as SASS, LESS or Stylus to our users, we’ve built a CSS workflow that utilizes the most helpful preprocessor features such as code blocks, modularity, CSS variables, etc. with just a little help from a PostCSS tool. You can now use plain CSS and have the ability to switch easily to SASS, LESS or Stylus - whenever you want to do it.
Everybody knows plain-old CSS, so we took it as a starting point. Pure CSS workflow is great to work with, since it is a standard and is dependency-free - however, it lacks modularity and is hard to maintain.
The idea was to take the best of both worlds - to build a modular CSS environment respecting DRY, object oriented and BEM principles without a lot of dependencies. The usage of PostCSS as a style transforming tool gave us the features we were missing.
PostCSS
PostCSS is a tool for transforming styles with JS plugins. Each plugin contains a limited functionality set, and the most common are:
- postcss-import - transforms
@import
rules by inlining content. - autoprefixer - parses CSS and add vendor prefixes to CSS rules using values from Can I Use.
- postcss-custom-media - transforms W3C CSS Custom Media Queries syntax to more compatible CSS.
- postcss-color-function - transforms W3C CSS color function to more compatible CSS.
- etc.
Configuring the Project
A whole process is powered by Node.js as a server and and Gulp as a task runner. Installation is handled through Bower and npm package managers configured in the gulp.js
file.
First we require PostCSS…
var gulp = require('gulp'),
postcss = require('gulp-postcss')
… and plugins:
var atImport = require('postcss-import');
var customMedia = require('autoprefixer');
After that, we reference plugins, source and destination CSS files:
gulp.task('styles-dist', function () {
var processors = [
atImport({
from: './src/themes/' + theme + '/src/app.css'
}),
autoprefixer({
browsers: ['last 2 versions']
}),
];
return gulp.src([
'./src/themes/' + theme + '/src/app.css'
])
.pipe(postcss(processors))
.pipe(gulp.dest('./dist/'));
});
Detailed instructions for configuration of gulp-postcss
can be found here.
Building code blocks
Since we use postcss-import
, our setup allows us to bundle CSS files on output, so we can write separate modular logical code blocks, as shown below:
We have divided CSS into small and logical code blocks to make them easier to overview and maintain. We use .vars
for variables, .tools
for externals, .modules
for reusable code, .components
for specific elements, etc.
Now we can use the @import
rule to import these code blocks into a single file named app.css
:
/*------------------------------------*\
# Application CSS file
\*------------------------------------*/
/*
* Variables
*/
@import url("vars.defaults.css");
/**
* Tools
* Various tools
*/
@import url("tools.fontface.css");
/**
* Generic styles
* Default style setup for reseting or normalizing our CSS
*/
@import url("generics.normalize.css");
@import url("generics.boxmodel.css");
/**
* Base styles
* The base global classes and styling
*/
@import url("base.globals.css");
@import url("base.typography.css");
/**
* Project specific modules
* All our modules such as buttons, inputs, labels, tables etc.
*/
@import url("modules.buttons.css");
@import url("modules.navigation.css");
/**
* Project specific components
* All project components such as headers, footer, bars, dropdowns etc.
*/
@import url("components.layout.css");
@import url("components.header.css");
@import url("components.footer.css");
/**
* Common classes
* Class helpers such as push, pull, radius etc.
*/
@import url("util.floats.css");
@import url("util.visibility.css");
This part is essential for our approach, because it keeps the code organized, easy to understand and edit. Also, it is good to follow DRY and BEM principles, as the resulting CSS workflow should be easy to follow and maintain.
Using variables
To increase the speed of development and the overall code quality, we can use CSS variables.
/*------------------------------------*\
# vars.colors
\*------------------------------------*/
:root {
--color-primary: red;
--color-secondary: gray;
}
We can put all sorts of values into variables, including colors, numeric values, font families, etc. Variables are extremely valuable for CSS maintenance and reliability purposes.
Vendor prefixes
Vendor prefixes are added automatically using plugin called autoprefixer
, based on the plugin configuration settings.
/*------------------------------------*\
# modules.buttons
\*------------------------------------*/
.btn {
border-radius: 3px;
}
will output:
.btn {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
Media Queries
We use custom media queries. We’ve added predefined variables for breakpoints that you can use.
/*------------------------------------*\
# vars.responsive
\*------------------------------------*/
@custom-media --from-small (min-width: 20em);
@custom-media --from-medium (min-width: 48em);
@custom-media --from-large (min-width: 64em);
usage:
.btn {
width: 100%;
}
@media (--from-medium) {
.btn {
width: 50%;
}
}
CSS rocks
Simplicity and avoidance of preprocessor technologies syntax were our main guidelines while developing this workflow. We hope you will be able to use it in your own projects no matter which preprocessing technology you use - if you use any. As always, please send us your comments or questions.
Feel free to leave a comment
comments powered by Disqus