In this tutorial I will show you how to create a Bootstrap theme for WordPress. If you just want the finished product, feel free to download the theme from my repository.

It's important to note that the theme has minimal styling, and serves as a blank slate for developers. The only assumption this theme makes is that you want to use Bootstrap. You can use as little or as much of the framework as you see fit. Below is the final result.

0. Install the WordPress Command Line

The WordPress Command Line is a Command line interface for WordPress. Simply put, this means that it can automate many tasks for us. One task is generating a starter theme. However, it can do much, much more.

1. Use the WordPress Command Line To Generate a New Starter Theme

Once you've installed the CLI, follow these steps to generate a base theme.

In the root of your WordPress install, run wp scaffold _s slug_for_your_theme --activate . For the case of this tutorial, I will run wp scaffold _s wordpress-bootstrap-starter-theme --activate . The wp scaffold _s function generates starter code for a theme based on _s (Underscores) .

. The Underscores Theme is created by Automatic, which is the same company that brings us WordPress. By default, it generates all files and directories needed for a valid WordPress theme.

Note that you can manually download the Underscores Theme, but using the CLI is much more effective.

If you navigate to the front end of your website, you should notice an underwhelming theme.

As underwhelming as it is, this starter theme meets all of WordPress's theme development standards. In short, this means that all the necessary templates, functions and css are generated for you.

2. Install and Configure WPGulp

Now that we have a base theme configured, we'll want it to be customized with Bootstrap's CSS, JS and template files. Using a build tool like WPGulp helps speed up this process.

WPGulp is an advanced & extensively documented Gulp.js + WordPress workflow. It can help you kick-start a build-workflow for your WordPress plugins and themes with Gulp.js, save you a lot of grunt work time, follow the DRY (Don't Repeat Yourself) principle, and #0CJS Zero-config JavaScript startup but still configurable via wpgulp.config.js file...

You can read about all of what WPGulp does, but some of my favorite features are...

Hot reloading

ES6 compiling

SASS compiling

Automatic image minification

Compiles all JS and CSS into one file each

Follow the docs to install WPGulp. In my case I cd into my theme by running cd wp-content/themes/wordpress-bootstrap-starter-theme/ . I then run npx wpgulp .

Once installation is complete, you'll want to edit the wpgulp.config.js file. Change the projectURL: 'wpgulp.local' variable to match your local development URL. If you plan on translating your site, make sure to update the translation options .



Now that WPGulp is installed and configured, we'll want to update our theme's file structure to match the recommendations in the wpgulp.config.js . These updates are based on the styleSRC , jsVendorSRC , jsCustomSRC and imgSRC variables.

Assuming you're still in your theme's directory, run the following commands. mkdir -p assets/css assets/js/vendor assets/js/custom assets/img/raw Now that the file structure matches the wpgulp.config.js configuration, we'll want to move existing .css and .js files by running the following commands. mv style.css assets/css/style.scss This moves the theme's style.css file into the assets/css , and also changes it to a .scss file. mv js/customizer.js assets/js/custom/customizer.js mv js/skip-link-focus-fix.js assets/js/custom/skip-link-focus-fix.js rm -R js/ These commands simply move existing javascript files generated by the wp scaffold _s command into the new javascript directory.

command into the new javascript directory. Note that we did not keep js/navigation.js . This is an opinionated script to handle mobile navigation that ships with _s . This isn't necessary since Bootstrap comes with it's own navbar component. rm -R layouts/ This directory is generated by the wp scaffold _s command, and is not needed since Bootstrap ships with a layout system.

At this point your assets directory should look like this.

4. Add Bootstrap

Now that we have a working a file structure, we can sprinkle in Bootstrap to our theme.

Download the latest version of Bootstrap At the time of this writing, the version is 4.3

Once unzipped, the folder should look something like this: Copy the scss directory and place it into assets/css/ Rename the assets/css/scss directory to assets/css/bootstrap Copy dist/js/bootstrap.js and place it into assets/js/vendor Create a new directory called base in assets/css/ Create the following files in assets/css/base _bootstrap_overrides.scss This file is where you will override the Bootstrap Variable Defaults _forms.scss This file will serve to automatically style form elements with Bootstrap's form component classes. _wordpress.scss This file will store WordPress Generated Classes

At this point your assets directory should look like this.

Now that we've loaded Bootstrap into our theme, we need to update the partials in the base directory. We also need to update the style.scss file.

Copy the css under # Forms in style.scss and paste into assets/css/base/_forms.scss

button, input[type="button"], input[type="reset"], input[type="submit"] { border : 1px solid ; border-color : #ccc #ccc #bbb ; border-radius : 3px ; background : #e6e6e6 ; color : rgba ( 0 , 0 , 0 , 0.8 ) ; font-size : 12px ; font-size : 0.75rem ; line-height : 1 ; padding : .6em 1em .4em ; } button:hover, input[type="button"]:hover, input[type="reset"]:hover, input[type="submit"]:hover { border-color : #ccc #bbb #aaa ; } button:active, button:focus, input[type="button"]:active, input[type="button"]:focus, input[type="reset"]:active, input[type="reset"]:focus, input[type="submit"]:active, input[type="submit"]:focus { border-color : #aaa #bbb #bbb ; } input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="search"], input[type="number"], input[type="tel"], input[type="range"], input[type="date"], input[type="month"], input[type="week"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="color"], textarea { color : #666 ; border : 1px solid #ccc ; border-radius : 3px ; padding : 3px ; } input[type="text"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="number"]:focus, input[type="tel"]:focus, input[type="range"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="week"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="color"]:focus, textarea:focus { color : #111 ; } select { border : 1px solid #ccc ; } textarea { width : 100% ; }

Update assets/css/base/_forms.scss with the following This allows us to style all form elements by extending Bootstrap's Form Component classes, rather than having to add these classes to the elements directly.

button, input[type="button"], input[type="reset"], input[type="submit"] { @extend .btn ; @extend .btn-primary ; } input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="search"], input[type="number"], input[type="tel"], input[type="range"], input[type="date"], input[type="month"], input[type="week"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="color"], textarea { @extend .form-control ; } select { @extend .form-control ; } textarea { @extend .form-control ; }

Add the following css to assets/css/base/_wordpress.scss These styles are taken from style.scss , which was originally style.css . This was generated by the wp scaffold _s command.

, which was originally . This was generated by the command. This file contains styles for WordPress Generated Classes.

#content[tabindex="-1"]:focus { outline : 0 ; } .alignleft { display : inline ; float : left ; margin-right : 1.5em ; } .alignright { display : inline ; float : right ; margin-left : 1.5em ; } .aligncenter { clear : both ; display : block ; margin-left : auto ; margin-right : auto ; } .sticky { display : block ; } .post, .page { margin : 0 0 1.5em ; } .updated:not(.published) { display : none ; } .page-content, .entry-content, .entry-summary { margin : 1.5em 0 0 ; } .page-links { clear : both ; margin : 0 0 1.5em ; } .comment-content a { word-wrap : break-word ; } .bypostauthor { display : block ; } .infinite-scroll .posts-navigation, .infinite-scroll.neverending .site-footer { display : none ; } .infinity-end.neverending .site-footer { display : block ; } .page-content .wp-smiley, .entry-content .wp-smiley, .comment-content .wp-smiley { border : none ; margin-bottom : 0 ; margin-top : 0 ; padding : 0 ; } embed, iframe, object { max-width : 100% ; } .custom-logo-link { display : inline-block ; } .wp-caption { margin-bottom : 1.5em ; max-width : 100% ; } .wp-caption img[class*="wp-image-"] { display : block ; margin-left : auto ; margin-right : auto ; } .wp-caption .wp-caption-text { margin : 0.8075em 0 ; } .wp-caption-text { text-align : center ; } .gallery { margin-bottom : 1.5em ; } .gallery-item { display : inline-block ; text-align : center ; vertical-align : top ; width : 100% ; } .gallery-columns-2 .gallery-item { max-width : 50% ; } .gallery-columns-3 .gallery-item { max-width : 33.33% ; } .gallery-columns-4 .gallery-item { max-width : 25% ; } .gallery-columns-5 .gallery-item { max-width : 20% ; } .gallery-columns-6 .gallery-item { max-width : 16.66% ; } .gallery-columns-7 .gallery-item { max-width : 14.28% ; } .gallery-columns-8 .gallery-item { max-width : 12.5% ; } .gallery-columns-9 .gallery-item { max-width : 11.11% ; } .gallery-caption { display : block ; }

Remove all css from style.scss , and replace with @import statements.

@import './base/bootstrap_overrides' ; @import './base/wordpress' ; @import './base/forms' ; @import './bootstrap/bootstrap' ;

Now that the assets directory is configured, we'll need to update the theme's functions.php file.

Open up your theme's functions.php file and scroll down to the Enqueue scripts and styles. section. It should look something like this.

function wordpress_bootstrap_starter_theme_scripts ( ) { wp_enqueue_style ( 'wordpress-bootstrap-starter-theme-style' , get_stylesheet_uri ( ) ) ; wp_enqueue_script ( 'wordpress-bootstrap-starter-theme-navigation' , get_template_directory_uri ( ) . '/js/navigation.js' , array ( ) , '20151215' , true ) ; wp_enqueue_script ( 'wordpress-bootstrap-starter-theme-skip-link-focus-fix' , get_template_directory_uri ( ) . '/js/skip-link-focus-fix.js' , array ( ) , '20151215' , true ) ; if ( is_singular ( ) && comments_open ( ) && get_option ( 'thread_comments' ) ) { wp_enqueue_script ( 'comment-reply' ) ; } } add_action ( 'wp_enqueue_scripts' , 'wordpress_bootstrap_starter_theme_scripts' ) ;

WPGulp will concatenate all javascript files in the assets/js/custom and assets/js/vendor directories into one file each. This means that we don't need to individually load navigation.js and skip-link-focus-fix.js anymore. Note that these files were generated by the wp scaffold _s command, and aren't required for every WordPress theme.

Remove the existing wp_enqueue_script functions and replace with the following. We load https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js as this is a requirement for Bootstrap.

as this is a requirement for Bootstrap. We added 'jquery' as an argument to the wp_enqueue_script function for the vendor-scripts. This is because Bootstrap requires jQuery.

as an argument to the function for the vendor-scripts. This is because Bootstrap requires jQuery. We added 'customize-preview' as an argument to the wp_enqueue_script function for the custom-scripts. This is because the /assets/js/custom/customizer.js file generated by the wp scaffold _s is only loaded on the theme customizer page.

as an argument to the function for the custom-scripts. This is because the file generated by the is only loaded on the theme customizer page. I chose to load the .min versions of each file, but you can load the unminified versions if you wish.

versions of each file, but you can load the unminified versions if you wish. The vendor.min.js and custom.min.js files will be generated once we run WPGulp.

function wordpress_bootstrap_starter_theme_scripts ( ) { wp_enqueue_style ( 'wordpress-bootstrap-starter-theme-style' , get_stylesheet_uri ( ) ) ; wp_enqueue_script ( 'wordpress-bootstrap-starter-theme-popper' , 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js' , array ( 'jquery' ) , '20151215' , true ) ; wp_enqueue_script ( 'wordpress-bootstrap-starter-theme-vendor-scripts' , get_template_directory_uri ( ) . '/assets/js/vendor.min.js' , array ( 'jquery' ) , '20151215' , true ) ; wp_enqueue_script ( 'wordpress-bootstrap-starter-theme-custom-scripts' , get_template_directory_uri ( ) . '/assets/js/custom.min.js' , array ( 'customize-preview' ) , '20151215' , true ) ; if ( is_singular ( ) && comments_open ( ) && get_option ( 'thread_comments' ) ) { wp_enqueue_script ( 'comment-reply' ) ; } } add_action ( 'wp_enqueue_scripts' , 'wordpress_bootstrap_starter_theme_scripts' ) ;

Run npm start to make sure everything is working. You should be able to open up http://localhost:3000/ and see your site. It should look similar to the following:

To ensure everything is hooked up correctly, let's override the $primary Bootstrap variable. Open assets/css/base/_bootstrap_overrides.scss and add $primary: green;

At this point we have everything we need to start building a Bootstrap theme. However, there are two small edits we should make. The current header.php file should look like this.

< div id = " page " class = " site " > < a class = " skip-link screen-reader-text " href = " #content " > <?php esc_html_e ( 'Skip to content' , 'wordpress-bootstrap-starter-theme' ) ; ?> </ a > < header id = " masthead " class = " site-header " > < div class = " site-branding " > <?php the_custom_logo ( ) ; if ( is_front_page ( ) && is_home ( ) ) : ?> < h1 class = " site-title " > < a href = " <?php echo esc_url ( home_url ( '/' ) ) ; ?> " rel = " home " > <?php bloginfo ( 'name' ) ; ?> </ a > </ h1 > <?php else : ?> < p class = " site-title " > < a href = " <?php echo esc_url ( home_url ( '/' ) ) ; ?> " rel = " home " > <?php bloginfo ( 'name' ) ; ?> </ a > </ p > <?php endif ; $wordpress_bootstrap_starter_theme_description = get_bloginfo ( 'description' , 'display' ) ; if ( $wordpress_bootstrap_starter_theme_description || is_customize_preview ( ) ) : ?> < p class = " site-description " > <?php echo $wordpress_bootstrap_starter_theme_description ; ?> </ p > <?php endif ; ?> </ div > < nav id = " site-navigation " class = " main-navigation " > < button class = " menu-toggle " aria-controls = " primary-menu " aria-expanded = " false " > <?php esc_html_e ( 'Primary Menu' , 'wordpress-bootstrap-starter-theme' ) ; ?> </ button > <?php wp_nav_menu ( array ( 'theme_location' = > 'menu-1' , 'menu_id' = > 'primary-menu' , ) ) ; ?> </ nav > </ header > < div id = " content " class = " site-content " >

Update the skip link class to use Bootstrap's screen reader, and remove the button.menu-toggle button, since that was specific to the _s theme.