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


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-beta.2 ** but you can find the latest one at

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.3.2
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.22s.

$ yarn add [email protected]
yarn add v1.3.2
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "[email protected] - 3".
warning " > [email protected]" has unmet peer dependency "[email protected]^1.12.3".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
└─ [email protected]
Done in 2.46s.

$ yarn add [email protected]>=3.0.0
yarn add v1.3.2
warning package.json: No license field
warning No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > [email protected]" has unmet peer dependency "[email protected]^1.12.3".
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
└─ [email protected]
warning No license field
Done in 4.33s.

$ yarn add [email protected]^1.12.3
yarn add v1.3.2
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.
└─ [email protected]
warning No license field
Done in 1.55s.

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 mode3

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="{{'/node_modules/jquery/dist/jquery.min.js' | prepend: site.baseurl}}"></script>
	<script src="{{'/node_modules/popper.js/dist/popper.min.js' | prepend: site.baseurl}}"></script>
	<script src="{{'/node_modules/bootstrap/dist/js/bootstrap.min.js' | prepend: site.baseurl}}"></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 !default4 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/functions";
@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 "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">

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


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/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="{{'/assets/vendor/jquery.min.js' | absolute_url}}"></script>
<script src="{{'/assets/vendor/popper.min.js' | absolute_url}}"></script>
<script src="{{'/assets/vendor/bootstrap.min.js' | absolute_url}}"></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 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-alpha.6 (
 * Copyright 2011-2017 The Bootstrap Authors
 * Copyright 2011-2017 Twitter, Inc.
 * Licensed under MIT (
/*! normalize.css v5.0.0 | MIT License | */
html {
  font-family: sans-serif;
  line-height: 1.15;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%; }


.content {
  font-size: 20px; 



    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 

  3. Safe mode disables custom plugins, and ignores symbolic links.

    The reason multiple load paths are shut off in safe mode is because of a fundamental inability to trust non-user content.

  4. SASS Variable Defaults: !default

    You can assign to variables if they aren’t already assigned by adding the !default flag to the end of the value. This means that if the variable has already been assigned to, it won’t be re-assigned, but if it doesn’t have a value yet, it will be given one.

    For example:

    $content: "First content";
    $content: "Second content?" !default;
    $new_content: "First time reference" !default;
    #main {
      content: $content;
      new-content: $new_content;

    is compiled to:

    #main {
      content: "First content";
      new-content: "First time reference"; }

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.