SaltyCrane Blog — Notes on JavaScript and web development

Customizing Bootstrap (Sass) using Grunt

Update 2015-12-02: I updated the post to use npm instead of Bower to install Bootstrap because it eliminates an extra tool and I hear this is the preferred method.

Update 2015-02-22: My co-workers informed me that Grunt is so last year and Gulp is the new hotness. You wish this post covered Gulp, but instead it covers Grunt.

I recently converted this blog to use Twitter's Bootstrap CSS framework to make it responsive. (In particular, I wanted to read it on my phone.) I was using Blueprint CSS from 2008 so it was about time for an update. Unfortunately my design is also from 2008 but I won't update that. (Web design is hard.)

My problem was that 2014 Bootstrap's 200px gutter width didn't match my 2008 design's 2px gutter. So I wanted to customize the gutter width.

There are a few ways to customize Bootstrap. One option is creating a custom version on Bootstrap's website and downloading it for use on your site. However this doesn't allow you to change things quickly as you develop. Another option is overriding Bootstrap's style in your site stylesheet. However, it's hacky to write compilcated selectors to override something in multiple places when it is set in a single variable in Bootstrap. For example, to change the gutter width, here is the single variable that needs to be changed.

Approach

Here is the approach I took to customize Bootstrap. I'm running Ubuntu 14.10 Utopic Unicorn 64-bit.

  • Install the Sass version of Bootstrap using npm. The standard Bootstrap project uses Less but my limited knowledge of frontend technology tells me Sass (specifically SCSS) is a better choice. I previously used Bower to install Bootstrap, but I learned that npm can now be used instead of Bower in most cases.
  • Override Bootstrap's Sass variables, remove unneeded components, and combine Bootstrap with my site's stylesheet using Sass @imports.
  • Compile the Sass SCSS files to CSS using Grunt and grunt-contrib-sass.
  • Commit the compiled Sass files to git. An alternative is to compile the files as part of the deploy process. At work, it is a pain keeping these files in git because there are many merge conflicts and differences in build tool versions and platforms between developers. For my blog, I am the only committer so I won't run into this.

Project directory structure

Here is what my project directory structure looks like:

my-project
├── Gruntfile.js
├── node_modules
│   ├── bootstrap-sass
│   ├── grunt
│   ├── grunt-contrib-sass
│   └── grunt-contrib-watch
├── package.json
├── sass
│   ├── _bootstrap-variables-override.scss
│   ├── _bootstrap.scss
│   └── mystyle.scss
└── static
    └── css
        └── mystyle.css

Install Sass

Sass is installed using RubyGems, the Ruby package manager.

$ sudo apt-get install ruby 
$ sudo gem install sass 
$ sass --version 
Sass 3.4.13 (Selective Steve)

Install grunt-cli

Grunt depends on npm, the Node.js package manager.

$ sudo add-apt-repository ppa:chris-lea/node.js 
$ sudo apt-get update 
$ sudo apt-get install nodejs 
$ npm --version
1.4.28
$ sudo npm install -g grunt-cli 
$ grunt --version 
grunt-cli v0.1.13

Install bootstrap-sass with npm

bootstrap-sass is the official Sass port of Bootstrap. I created this package.json file, /tmp/my-project/package.json. For more information about the package.json file, see the npm documentation.

{
  "name": "my-project",
  "version": "0.1.0",
  "dependencies": {
    "bootstrap-sass": "3.3.1"
  }
}

Then I ran npm install to download the files. The packages are stored in the node_modules directory.

$ cd /tmp/my-project 
$ npm install 

Customize Boostrap

To customize Boostrap, I copied the node_modules/bootstrap-sass/assets/stylesheets/boostrap.scss file to my sass directory, removed the components that I didn't need, and added a line for my custom variable overrides. Here is my final file, named /tmp/my-project/sass/_bootstrap.scss:

// Variable overrides come first (without !default). Bootstrap default variables
// come second because they use !default (they won't get set if set already) and
// some of them depend on our overrides.
@import "bootstrap-variables-override";

// Core variables and mixins
@import "bootstrap/variables";
@import "bootstrap/mixins";

// Reset and dependencies
@import "bootstrap/normalize";
@import "bootstrap/print";
@import "bootstrap/glyphicons";

// Core CSS
@import "bootstrap/scaffolding";
@import "bootstrap/type";
@import "bootstrap/code";
@import "bootstrap/grid";
@import "bootstrap/tables";
@import "bootstrap/forms";
@import "bootstrap/buttons";

// // I am not using these components so I commented them out to make the CSS file smaller
// // Components
// @import "bootstrap/component-animations";
// @import "bootstrap/dropdowns";
// @import "bootstrap/button-groups";
// @import "bootstrap/input-groups";
// @import "bootstrap/navs";
// @import "bootstrap/navbar";
// @import "bootstrap/breadcrumbs";
// @import "bootstrap/pagination";
// @import "bootstrap/pager";
// @import "bootstrap/labels";
// @import "bootstrap/badges";
// @import "bootstrap/jumbotron";
// @import "bootstrap/thumbnails";
// @import "bootstrap/alerts";
// @import "bootstrap/progress-bars";
// @import "bootstrap/media";
// @import "bootstrap/list-group";
// @import "bootstrap/panels";
// @import "bootstrap/responsive-embed";
// @import "bootstrap/wells";
// @import "bootstrap/close";

// // Components w/ JavaScript
// @import "bootstrap/modals";
// @import "bootstrap/tooltip";
// @import "bootstrap/popovers";
// @import "bootstrap/carousel";

// Utility classes
@import "bootstrap/utilities";
@import "bootstrap/responsive-utilities";

The Boostrap variables are located in node_modules/bootstrap-sass/assets/stylesheets/bootstrap/_variables.scss I copied the ones that I was interested in into a _bootstrap-variables-override.scss file. Be sure to remove the !default flag and import this file before the default bootstrap variables. For more information about !default, see the Sass documentation. Here is my /tmp/my-project/sass/_bootstrap-variables-override.scss file:

// overrides here (do not use !default here)
$grid-gutter-width: 10px;
$font-size-base: 16px;
$headings-font-weight: 800;

In my site stylesheet, I import _bootstrap.scss. Here is my /tmp/my-project/sass/mystyle.scss file:

@import "bootstrap";
/* add site style here */

Install grunt plugins

To compile the Sass files to CSS, I am using grunt-contrib-sass. I added a devDependencies section to my package.json file which contains grunt and two grunt plugins. Here is my /tmp/my-project/package.json. For more information about installing plugins, see the Grunt documentation.

{
  "name": "my-project",
  "version": "0.1.0",
  "dependencies": {
    "bootstrap-sass": "3.3.1"
  },
  "devDependencies": {
    "grunt": "0.4.5",
    "grunt-contrib-sass": "0.8.1",
    "grunt-contrib-watch": "0.6.1"
  }
}

I ran npm install again to install grunt, grunt-contrib-sass, and grunt-contrib-watch.

$ cd /tmp/my-project 
$ npm install 

Create Gruntfile.js

My Gruntfile.js uses the grunt-contrib-sass plugin to compile the Sass to CSS. My input file is sass/mystyle.scss and my output file is static/css/mystyle.css. I set the loadPath so that Sass can find the Bootstrap SCSS files in the node_modules directory to import. Alternatively, I could specify the full path to import in /tmp/my-project/sass/_bootstrap.scss. For example: @import "../node_modules/bootstrap-sass/assets/stylesheets/bootstrap/variables"; I am also using the grunt-contrib-watch plugin during development to automatically run the compilation whenever a SCSS file changes. For more information on configuring tasks, see the Grunt documentation. Here is my Gruntfile, /tmp/my-project/Gruntfile.js:

module.exports = function(grunt) {
    grunt.initConfig({
        sass: {
            // this is the "dev" Sass config used with "grunt watch" command
            dev: {
                options: {
                    style: 'expanded',
                    // tell Sass to look in the Bootstrap stylesheets directory when compiling
                    loadPath: 'node_modules/bootstrap-sass/assets/stylesheets'
                },
                files: {
                    // the first path is the output and the second is the input
                    'static/css/mystyle.css': 'sass/mystyle.scss'
                }
            },
            // this is the "production" Sass config used with the "grunt buildcss" command
            dist: {
                options: {
                    style: 'compressed',
                    loadPath: 'node_modules/bootstrap-sass/assets/stylesheets'
                },
                files: {
                    'static/css/mystyle.css': 'sass/mystyle.scss'
                }
            }
        },
        // configure the "grunt watch" task
        watch: {
            sass: {
                files: 'sass/*.scss',
                tasks: ['sass:dev']
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-sass');
    grunt.loadNpmTasks('grunt-contrib-watch');
    // "grunt buildcss" is the same as running "grunt sass:dist".
    // if I had other tasks, I could add them to this array.
    grunt.registerTask('buildcss', ['sass:dist']);
};

Run grunt

To compile the Sass to CSS for production, I run grunt buildcss. This creates the output CSS file /tmp/my-project/static/css/mystyle.css.

$ cd /tmp/my-project 
$ grunt buildcss 

During development, I use grunt watch to build whenever a SCSS (Sass) file changes.

$ cd /tmp/my-project 
$ grunt watch 

Other Notes

See Also / References

Comments


#1 josephine commented on :

Hi! Thanks for the article. It helped me a lot.
I have an issue adding javascript to the project. Generally, don't really know how to do it. Documentation doesn't say much.
Any help will be much appreciated.

disqus:1902807857


#2 Jason commented on :

Thanks for sharing. I was looking for some info on customizing bootstrap and found this very informative article. Although I consider myself somewhat of a fullstack developer (meaning I have done frontend and backend dev) after reading this post it reminds I how much I love working mostly on the backend these days :)

disqus:2047234271


#3 spacepope nancho commented on :

First of all, thanks for the article. I followed it step by step and didn't encounter a single problem.

To answer Josephine's question: Bootstrap indeed uses css and js (and glyphicons). To build the javascript, you can use Grunt's concat method. It concats javascript files into one file. You can specify the files you want, or simply combine them all (as in my example)

In your Gruntfile.js file add the following lines inside grunt.initConfig({

concat: {
bootstrap: {
src: ['node_modules/bootstrap-sass/assets/javascripts/bootstrap/*.js'],
dest: 'public/assets/js/bootstrap.js'
}
},
As for the glyphicons (and images), you can use grunt-contrib-copy:

copy: {
bootstrap: {
expand: true,
cwd: 'node_modules/bootstrap-sass/assets/fonts/bootstrap/',
src: ['*'],
dest: 'public/assets/fonts/bootstrap/'
}
},
You also need to add this:

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-copy');

And finally:

grunt.registerTask('bootstrap', ['sass:bootstrap', 'copy:bootstrap', 'concat:bootstrap']);

Execute with: grunt bootstrap

=> you can optimize the script some more (like putting the long path in a variable), but this should at least get you going.

disqus:2396008796


#4 Eliot commented on :

Thanks for posting the config for the javascript and fonts!

disqus:2397032014


#5 Dan Brellis commented on :

Thanks for the great article. I'm able to do everything you list, but wanted to know how to then take mystyle.css and minify to mystyle.min.css ?? Any help is appreciated. Thanks!

disqus:2485303138


#6 Joe commented on :

Great post, finally one that shows how to override boostrap sass variables WITHOUT vendoring bootstrap. I value that a lot, since it will greatly reduce the risk of making custom changes to bootstrap specifics (not in a override file).

disqus:3113995388


#7 lemonice commented on :

Awesome, thanks for the help. This cleared up a lot of confusion I had with Sass file structure + Grunt + Bootstrap all at once.

disqus:3187966744


#8 jeezysevenRWD commented on :

Greetings, is there a way you could redo an update using Bootstrap 3.3.7 and note if there are any differences after using grunt.

disqus:3478271918