Let’s create a Drupal 9 theme for our Drupal website.There are a lot of base themes you can use, but in this tutorial I want to create a custom theme that has Bootstrap 5 integration via npm.

Step 1: theme folder and .info file

  • To the themes folder we add folders custom/mytheme.
  • In the mytheme folder, add a file mytheme.info.yml:

To mytheme.info.yml:

name: My theme
type: theme
base theme: false
description: 'Custom theme MyTheme'
package: Other
core_version_requirement: ^9
  header: 'Header'
  pre_content: 'Pre-content'
  breadcrumb: Breadcrumb
  highlighted: Highlighted
  help: Help
  content: Content
  page_top: 'Page top'
  page_bottom: 'Page bottom'
  sidebar_first: 'First sidebar'

Now Drupal is able to recognize and enable your theme under /admin/appearance ("install and set as default")

optional: you can add a screenshot thumbnail to mytheme/screenshot.png and add it as a key to mytheme.info.yml:

screenshot: screenshot.png

Step 2: add libraries

Next, we want to add the proper styling (CSS & js). We do this by adding a mytheme.libraries.yml file:

  version: 1.X
    js/scripts.js: {}
      css/style.css: {}

Now we add the libraries file to our mytheme.info.yml:

name: My theme
type: theme
base theme: stable
description: 'Custom theme MyTheme'
package: Other
core_version_requirement: ^9
  - mytheme/global
  header: 'Header'
  pre_content: 'Pre-content'
  breadcrumb: Breadcrumb
  highlighted: Highlighted
  help: Help
  content: Content
  page_top: 'Page top'
  page_bottom: 'Page bottom'
  sidebar_first: 'First sidebar'

Now you can add your required CSS and JS files:

  • CSS styles to mytheme/css/style.css
  • Custom javascript to mytheme/js/scripts.js

Step 3: add Bootstrap 5 et al. dependencies

We want to have Bootstrap 5 as a dependency and we want to use the latest. I prefer not to use other themes as my base theme and want to add the npm packages right away. I add a file custom/mytheme/package.json containing the following:

  "name": "mytheme",
  "version": "1.0.0",
  "description": "Custom Drupal theme with Bootstrap 5",
  "main": "gulpfile.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "find node_modules/ -name '*.info' -type f -delete"
  "author": "Stefvanlooveren <stefvanlooveren.me>",
  "dependencies": {
    "bootstrap": "^5.0.1",
    "bootstrap-icons": "^1.5.0",
    "@popperjs/core": "^2.9.2"
  "devDependencies": {
    "browser-sync": "^2.26.14",
    "gulp": "^4.0.2",
    "gulp-autoprefixer": "^7.0.1",
    "gulp-babel": "^8.0.0",
    "gulp-sass": "^4.1.0",
    "gulp-sass-lint": "^1.4.0",
    "gulp-watch": "^5.0.1",
    "gulp-uglify": "^3.0.2"

Next, we also want to create a gulpfile.js for some front-end awesomeness:

To custom/mytheme/gulpfile.js, add:

let gulp = require('gulp'),
    sass = require('gulp-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    browserSync = require('browser-sync').create(),
    uglify = require('gulp-uglify');

const paths = {
    scss: {
        src: './scss/style.scss',
        dest: './css',
        watch: './scss/**/*.scss'
    js: {
        bootstrap_src: './node_modules/bootstrap/dist/js/bootstrap.min.js',
        bootstrap_dest: './js/bootstrap',
        popper: './node_modules/@popperjs/core/dist/umd/popper.min.js',
        popper_dest: './js/popper',
        theme_src: './js/src/*.js',
        theme_dest: './js'
    twig: {
        watch: './templates/**/*.twig'

// Compresss theme JS
function buildjs() {
    return gulp

// Compile sass into CSS & auto-inject into browsers
function compile() {
    var sassOptions = {
        outputStyle: 'expanded',
        indentType: 'space',
        indentWidth: 2,
        linefeed: 'lf'

    return gulp
        .src([paths.scss.src], { sourcemaps: true })
        // .src([paths.scss.src])
        .pipe(sass(sassOptions).on('error', sass.logError))
        // .pipe(gulp.dest(paths.scss.dest))
        .pipe(gulp.dest([paths.scss.dest], { sourcemaps: true }))

// Move the Bootstrap JavaScript files into our js/bootstrap folder.
function move_bootstrap_js_files() {
    return gulp

// Move the Popper JavaScript files into our js/popper folder.
function move_popper_js_files() {
    return gulp

// Watching scss files.
function watch() {

        proxy: "bs5.localhost:8888",
        open: false

    // gulp.watch([paths.js.theme_src], buildjs).on('change', browserSync.reload);
    gulp.watch([paths.js.theme_src], buildjs);
    gulp.watch([paths.scss.watch], compile);
    gulp.watch([paths.twig.watch]).on('change', browserSync.reload);

const build = gulp.series(buildjs, compile, move_bootstrap_js_files, move_popper_js_files, gulp.parallel(watch));

exports.buildjs = buildjs;
exports.compile = compile;
exports.move_bootstrap_js_files = move_bootstrap_js_files;
exports.move_popper_js_files = move_popper_js_files;
exports.watch = watch;

exports.default = build;

Now run

npm install

The result is this:

[10:49:29] Using gulpfile /var/www/html/web/themes/custom/mytheme/gulpfile.js
[10:49:29] Starting 'default'...
[10:49:29] Starting 'buildjs'...
[10:49:29] Finished 'buildjs' after 73 ms
[10:49:29] Starting 'compile'...
[10:49:29] Finished 'compile' after 84 ms
[10:49:29] Starting 'move_bootstrap_js_files'...
[10:49:29] Finished 'move_bootstrap_js_files' after 4.76 ms
[10:49:29] Starting 'move_popper_js_files'...
[10:49:29] Finished 'move_popper_js_files' after 2.84 ms
[10:49:29] Starting 'watch'...
[Browsersync] Proxying: http://mytheme.localhost:8888
[Browsersync] Access URLs:
       Local: http://localhost:3000
          UI: http://localhost:3001
 UI External: http://localhost:3001

What will these scripts do?

First of all it will download Bootstrap to the node_modules and automatically copy a minified version to js/bootstrap/bootstrap.min.js.

To include it, we add a new library to custom/mytheme/mytheme.libraries.yml:

  version: '5.0.1'
    js/bootstrap/bootstrap.min.js: { minified: true }
    - core/jquery

Don't for get to clear caches. But we are not ready yet.

To get started with including the Bootstrap styles, create a new file in custom/mytheme/scss/style.scss. Gulp will then watch for changes and copy them to custom/mytheme.css/style.css.

To the custom/mytheme/scss/style.scss file, add the bootstrap dependencies:

// --------------------------------------------------------
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
// --------------------------------------------------------

@import "../node_modules/bootstrap/scss/functions";

// --------------------------------------------------------
// 2. Include your variable overrides here
// --------------------------------------------------------

@import "variables"; // These override default Bootstrap variables

// --------------------------------------------------------
// 3. Include remainder of required Bootstrap stylesheets
// --------------------------------------------------------

@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";

// --------------------------------------------------------
// 4. Include any optional Bootstrap components as you like
// --------------------------------------------------------

@import "../node_modules/bootstrap/scss/root";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
//@import "../node_modules/bootstrap/scss/tables";
@import "../node_modules/bootstrap/scss/forms";
@import "../node_modules/bootstrap/scss/buttons";
@import "../node_modules/bootstrap/scss/transitions";
//@import "../node_modules/bootstrap/scss/dropdown";
//@import "../node_modules/bootstrap/scss/button-group";
@import "../node_modules/bootstrap/scss/nav";
@import "../node_modules/bootstrap/scss/navbar";
//@import "../node_modules/bootstrap/scss/card";
//@import "../node_modules/bootstrap/scss/accordion";
@import "../node_modules/bootstrap/scss/breadcrumb";
@import "../node_modules/bootstrap/scss/pagination";
//@import "../node_modules/bootstrap/scss/badge";
@import "../node_modules/bootstrap/scss/alert";
//@import "../node_modules/bootstrap/scss/progress";
@import "../node_modules/bootstrap/scss/list-group";
@import "../node_modules/bootstrap/scss/close";
//@import "../node_modules/bootstrap/scss/toasts";
@import "../node_modules/bootstrap/scss/modal";
//@import "../node_modules/bootstrap/scss/tooltip";
//@import "../node_modules/bootstrap/scss/popover";
//@import "../node_modules/bootstrap/scss/carousel";
//@import "../node_modules/bootstrap/scss/spinners";
//@import "../node_modules/bootstrap/scss/offcanvas";
@import "../node_modules/bootstrap/scss/utilities";

// Utilities
@import "../node_modules/bootstrap/scss/utilities/api";

To the custom/mytheme/scss/_variables.scss file, add the contents of this file: this allows you to override settings as colors, grids, etc. Full control over the bootstrap template!

Clear the Drupal cache and you will have loaded all the JS and CSS dependencies you need.

Step 4: override the core drupal building blocks with template files

So you would probably want to "bootstrapify" your typical building blocks like forms, views, navigation etc. Thijs Boots has a good starterkit on Github. You can copy the "templates" folder into custom/mytheme.

Now if you clear caches again, you will see your sidebars, buttons, navigation etc:

Drupal bootstrap 5 theme

We are done!

Shout out to this Github repository (Thijs Boots)