5 Steps To Add Bootstrap 4 To Jekyll The Right Way

  • Published: February 27, 2017


One of the keys to use Bootstrap successfully is to be able to use and redefine its variables in our custom designs. We should not simply add Bootstrap’s javascript and CSS stylesheets to use its components, we need to change them and not making all the web look boringly the same.

This is a guide to make it easy to use Bootstrap 4 with a Jekyll website and be able to use and customize its variables as well as defining new ones.


Jekyll provides built-in support for syntactically awesome stylesheets (Sass).

Sass is a CSS extension language, it provides:

  • Variables
  • Nesting elements
    • Loops
    • Arguments
  • Selector inheritance

It consists of two syntaxes:

  • the original, indented, syntax uses the .sass extension.
  • the newer syntax, more similar to CSS, uses .scss extension.

To make Jekyll process these SASS files, we need to create files with the proper extension name (.scss or .sass) and start the file contents with two lines of triple dashes.

A file named assets/main.scss will be rendered like assets/main.css.

As Bootstrap switched from Less to Sass1 now we can use it directly without relying in parallel projects like bootstrap-sass needed for Bootstrap 3.

Installing Bootstrap 4

We will be using the package manager Yarn to install Bootstrap. At our Jekyll website root folder we run yarn install. In this case I will be using Bootstrap v4.0.0 but you can find the latest one at https://getbootstrap.com/docs/4.0/getting-started/download/.

Previously I used [Bower] in this tutorial but as it won't be maintained anymore, [Yarn] is a better, more robust solution.

$ yarn install
yarn install v1.5.1
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
info Lockfile not saved, no dependencies.
Done in 0.16s.

$ yarn add bootstrap@4.0.0
yarn add v1.5.1
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > bootstrap@4.0.0" has unmet peer dependency "jquery@1.9.1 - 3".
warning " > bootstrap@4.0.0" has unmet peer dependency "popper.js@^1.12.9".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ bootstrap@4.0.0
info All dependencies
└─ bootstrap@4.0.0
Done in 1.16s.

$ yarn add jquery
yarn add v1.5.1
warning package.json: No license field
warning No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > bootstrap@4.0.0" has unmet peer dependency "popper.js@^1.12.9".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ jquery@3.3.1
info All dependencies
└─ jquery@3.3.1
warning No license field
Done in 1.15s.

$ yarn add popper.js
yarn add v1.5.1
warning package.json: No license field
warning No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ popper.js@1.14.3
info All dependencies
└─ popper.js@1.14.3
warning No license field
Done in 1.02s.

So far, we have added this structure (excluding jekyll files):

$ tree -L 2
├── node_modules
│   ├── bootstrap
│   ├── jquery
│   └── popper.js
├── package.json
└── yarn.lock

4 directories, 2 files

If you aren’t working in a Jekyll project, you can add it now to the current directory with jekyll new . –force.

Adding new Sass load paths

We need to add this new path so Jekyll can process its Sass files.

Jekyll will look at the folder specified by the sass_dir configuration key (/_sass by default), but it also supports extending it, so it will process other folders too.

Instead of setting a custom sass folder with:

    sass_dir: _sass

we use the load-paths2 key in _config.yml to add more paths:

        - _sass
        - node_modules
load_paths only works when not in safe mode[^safe-mode] (i.e. it won't work with Github Pages.)

Whitelist node_modules

Jekyll 3.3 started excluding the node_modules directory by default, so we have to make sure it is included in the final site to be able to use its files:

exclude: []

Add javascript

Add Bootstrap JavaScript at the end of the document so the pages load faster, just before the </body> HTML tag.

We add them in the default layout at _layouts/default.html or in footer.html in the _includes folder:

	<script src="{% raw %}{{'/node_modules/jquery/dist/jquery.min.js' | prepend: site.baseurl}}"{% endraw %}></script>
	<script src="{% raw %}{{'/node_modules/popper.js/dist/umd/popper.min.js' | prepend: site.baseurl}}{% endraw %}"></script>
	<script src="{% raw %}{{'/node_modules/bootstrap/dist/js/bootstrap.min.js' | prepend: site.baseurl}}{% endraw %}"></script>

Import Bootstrap and use Sass variables

Create variables Sass partial

To be able to define new variables and reuse the ones defined in Bootstrap, we create a new partial Sass file _sass/_variables.scss.

  1. We define our variables
  2. “Overwrite” the ones we want from Bootstrap node_modules/bootstrap/scss/_variables.scss before loading them and then
  3. we import the Bootstrap variables.
All the variables defined in Bootstrap 4 have the !default**[^scss_default] property at the end. When Jekyll process each Scss file, it only defines the variables that do not have been assigned any value yet, so we can define Bootstrap's variables before Bootstrap itself define them. It is important to do this before importing the variables because there are many of them depending on each other to calculate CSS properties values.

In _sass/_variables.scss:

$custom-font-size: 20px;
@import "../node_modules/bootstrap/scss/variables";

Import variables from main Sass file

After we have our variables, we import them from our main style sheet:

In assets/main.scss we import them and then work with our styles, using the above variables:


@import "bootstrap/scss/functions";
@import "variables";
@import "bootstrap/scss/bootstrap";

.content {
  font-size: $custom-font-size;
Don't miss the triple dashes at the beginning of the file to ensure Jekyll reads the file to be transformed into CSS later

Add css to layout

After we have our assets/main.scss, Jekyll will process it and generate the final CSS file: assets/main.css.

That is the path we need to add to our layout, in the <head> section of _layouts/default.html we include the css: <link rel="stylesheet" href="/assets/main.css">

<!-- site css -->
<link rel="stylesheet" href="{{'/assets/main.css' | prepend: site.baseurl}}">

All together

This is the basic flow Jekyll follows processing these Scss files to generate assets/main.css:

graph TB STYLESSCSS["Jekyll reads /assets/main.scss"]-->partials{"imports Sass partials"} partials--> |"import variables"|VARS partials--> |"import bootstrap/scss/bootstrap"|BS_SCSS VARS["/_sass/_variables.scss"]-->BS_VARIABLES BS_VARIABLES["@import '../node_modules/bootstrap/scss/variables';"]-->STYLESCSS BS_SCSS["/node_modules/bootstrap/scss/bootstrap.scss"]-->STYLESCSS STYLESCSS["Generates /assets/main.css"]



You’ll find a *minimal example site hosted on Github showing the result of following this tutorial at https://github.com/marcanuy/jekyll-bootstrap4).


There is also a step by step video using the previous package manager used here Bower instead of Yarn and a previous version of Bootstrap, so you can get a general idea of how this works but it won’t reflect the current status of the tutorial:


It is also part of a Jekyll starter site jekyll-skeleton.

OPTIONAL: Keep node_modules out of _site

You probably don’t want to expose all the package files in your website, nor do I, so let’s see how to serve just only the needed files.

To make this we will copy the files that we are including directly from the node_modules directory to a new one containing just these files in each build, that means, we have to set up a script replacement for jekyll build and jekyll serve.

In this case I will be using the classic make program, each task is pretty self explanatory and can also be ported easily to Grunt or any other task automation solution.


Create a file called Makefile at root level with this content (if using Bundler):

SHELL := /bin/bash
BUNDLE := bundle
YARN := yarn
VENDOR_DIR = assets/vendor/
JEKYLL := $(BUNDLE) exec jekyll

PROJECT_DEPS := Gemfile package.json

.PHONY: all clean install update

all : serve

	$(JEKYLL) doctor
	$(HTMLPROOF) --check-html \
		--http-status-ignore 999 \
		--internal-domains localhost:4000 \
		--assume-extension \

install: $(PROJECT_DEPS)
	$(BUNDLE) install --path vendor/bundler
	$(YARN) install

update: $(PROJECT_DEPS)
	$(BUNDLE) update
	$(YARN) upgrade

	mkdir -p $(VENDOR_DIR)
	cp node_modules/jquery/dist/jquery.min.js $(VENDOR_DIR)
	cp node_modules/popper.js/dist/umd/popper.min.js $(VENDOR_DIR)
	cp node_modules/bootstrap/dist/js/bootstrap.min.js $(VENDOR_DIR)

build: install include-yarn-deps
	$(JEKYLL) build

serve: install include-yarn-deps
	JEKYLL_ENV=production $(JEKYLL) serve

It is just a wrapper of Jekyll build and install commands handling dependencies.

Remember that those spaces are TAB's or make will fail.

To make it easier, I’ve created a gist with the above script in two flavours:

  • Makefile a Gemfile (not using Bundler).
  • Makefile using Bundler.

Now we will use make build and make serve to work with Jekyll as wrappers of jekyll build and jekyll serve respectively.

Remember that Makefile content uses **TABS** to indent recipes instead of *spaces*.

Using new paths

It just remain to update our paths in the layout, in default.html use them as:

<script src="{%raw%}{{'/assets/vendor/jquery.min.js' | absolute_url}}{%endraw%}"></script>
<script src="{%raw%}{{'/assets/vendor/popper.min.js' | absolute_url}}{%endraw%}"></script>
<script src="{%raw%}{{'/assets/vendor/bootstrap.min.js' | absolute_url}}{%endraw%}"></script>

Exclude unwanted directories

Tell Jekyll to not include the node_modules directory in the final site, in _config.yml:

 - node_modules
This is the default behaviour from Jekyll 3.3


Now we are just including in our website the files we chose from the node_modules folder, placing them in assets/vendor and avoiding to have any other unnecessary files in the final website.


When we build our site, Jekyll will process the .scss files with our custom variables and we will have them in our _site/assets/main.css. In this example its content starts with the Bootstrap code and ends with our custom _main.scss processed, looking like:

 * Bootstrap v4.0.0 (https://getbootstrap.com)
 * Copyright 2011-2018 The Bootstrap Authors
 * Copyright 2011-2018 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
:root {
  --blue: #007bff;
  --indigo: #6610f2;
  --purple: #6f42c1;


.content {
  font-size: 20px; }


*[CSS]: Cascading Style Sheets

  1. http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/

    Moved from Less to Sass. Bootstrap now compiles faster than ever thanks to Libsass, and we join an increasingly large community of Sass developers.

  2. Issue referring the code at https://github.com/jekyll/jekyll-sass-converter/blob/master/lib/jekyll/converters/scss.rb#L77 [return]
Marcelo Canina
I'm Marcelo Canina, a developer from Uruguay. I build websites and web-based applications from the ground up and share what I learn here.
comments powered by Disqus

Guide to add Bootstrap 4 to Jekyll with focus on having also a CSS stylesheet using its own variables and custom ones.

Except as otherwise noted, the content of this page is licensed under CC BY-NC-ND 4.0 ·