Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to customize Bootstrap 4 using NPM and SASS?

I'm losing my mind trying to customize Bootstrap 4. Literally every single YouTube video, blog post, and Bootstrap's own documentation are either lacking in sufficient information, confusing as hell, or outdated and no longer relevant. Here's what I know:

  • I know I need to use NPM to download Bootstrap to my project directory
  • I know I'm not supposed to make changes directly to Bootstrap's source files
  • I know I need to set up a custom SCSS file and import Bootstrap functions into this file
  • I know I need to "call" Bootstrap after I make changes in my SCSS file
  • I know I need to set up, run, and compile Sass

My questions:

  • What's the difference between node-sass and regular sass and which one do I use?
  • What's the correct way to install Sass and set it up to watch for changes?
  • If I want to use Google fonts, where do I import/link? In the HTML? In my custom SCSS file? Both?
  • What is the correct way to import the Bootstrap functions I want to override?

I know this is a lot but seriously, there are a billion conflicting tutorials online and none of them are working for me. Any help is greatly appreciated!

like image 834
Chris Avatar asked Jul 13 '20 18:07

Chris


People also ask

Does Bootstrap 4 Use Sass?

But since version 4, Bootstrap uses only Sass. The source code for the Bootstrap 4 and 5 frameworks is written entirely in Sass, which is a testimony to how mature Sass has become. You may have heard the tagline, "Sass is CSS with superpowers".

Can you use Sass and Bootstrap together?

In your custom.scss , you'll import Bootstrap's source Sass files. You have two options: include all of Bootstrap, or pick the parts you need. We encourage the latter, though be aware there are some requirements and dependencies across our components.

How do I customize Bootstrap components?

If you want to customize your Bootstrap site, leave the source code as-is and simply add custom code in an external stylesheet. The code in this external stylesheet will override the existing styles, as long as it's set up properly. This set-up process differs slightly depending on how you load Bootstrap on your site.


1 Answers

What's the difference between node-sass and regular sass and which one do I use?

  • Bootstrap 4 uses scss (Compass, which is built around sass)
  • they use similar syntax but there's a difference in some of the functions
  • node-sass is a library that provides binding for Node. It allows you to natively compile .scss files to css. Since using package managers like NPM, you'll be using Node.js. This library binds to your environment and Node version, to pre-process your scss files.
  • using a css watcher in gulp, requires other packages to make it work
  • you don't need gulp for a watcher, but then you most likely will have to install Ruby Compass (which is a bit outdated as a setup these days, I could be wrong)

What's the correct way to install Sass and set it up to watch for changes?

So hold on, you're already decided to use sass or are you referring to scss? The right thing to do is to stick with what's in the Bootstrap package. If you use a package.json file, and you should, you'll be needing node-sass (I think). Probably there are other packages out there, but that's how I know it works.

Another thing to think about is the transpiler, like Babel. Babel requires a piece of setup in the package.json or in a .browserslistrc file.

So some essentials in the package.json:

{
    "dependencies": {
        "bootstrap": "^4.3.1", // your bs version
        "font-awesome": "^4.7.0", // we'll get to fonts later
        "jquery": "^3.4.1", // bs dependency
        "popper.js": "^1.15.0" // bs dependency
    },
    "devDependencies": {
        "@babel/core": "^7.5.5", // transpiler requirement
        "@babel/preset-env": "^7.5.5", // transpiler requirement
        "debounce": "^1.2.0", // gulp task, depends on your Gulpfile.js
        "es6-promise": "^4.2.8",
        "eslint": "^5.16.0", 
        "graceful-fs": "^4.2.2",
        "gulp": "^4.0.2", // your task runner Gulp
        "gulp-autoprefixer": "^6.1.0", // gulp util
        "gulp-babel": "^8.0.0-beta.2", // transpiler for gulp tasks
        "gulp-clean": "^0.4.0", // gulp util
        "gulp-clean-css": "^3.10.0",
        "gulp-cli": "^2.2.0", // gulp requirement
        "gulp-concat": "^2.6.1", // gulp util
        "gulp-fs-cache": "^0.1.0", // gulp util
        "gulp-modernizr": "^3.3.0", // great way to detect js requirements
        "gulp-sass": "^4.0.2", // node-sass version for gulp
        "gulp-sourcemaps": "^2.6.5", // gulp util
        "gulp-uglify": "^3.0.2", // gulp util
        "gulp-util": "^3.0.8", // hey gulp util
        "gulp-watch-sass": "^1.4.1", // css compiler
        "invariant": "^2.2.4", // not sure, probably got some warning
        "minimatch": "^3.0.4", // not sure, probably got some warning
        "node-gyp": "^3.8.0", // meh
        "npm": "^6.11.2", // your package manager
        "requirejs": "^2.3.6", // requirements in Gulpfile.js
        "stream-series": "^0.1.1", // task util
        "strip-ansi": "^4.0.0" // not sure, probably got some warning
    },
    "browserslist": [
        "> 0.5%",
        "not ie <= 10",
        "not dead"
    ]
}

So ok, not really node-sass but gulp-sass is a wrapper around node-sass, which in turn binds to your environment. It's easy to get mixed with packages these days, I know.

The Babel setup can be changed by using https://browserl.ist/. This is used for your autoprefixer in CSS. No need to write -webkit-blabla anymore. Depending on the browserslist setup, it will pick this for you.

If I want to use Google fonts, where do I import/link? In the HTML? In my custom SCSS file? Both?

What I prefer is to copy the package scss files and create a fonts.scss in the root. From there we can import google fonts or custom fonts like font-awesome.

My fonts.scss file

@import '_variables/myprojectname.custom.variables';

/*
 * font-weight: 100; // Thin (Hairline)
 * font-weight: 200; // Extra Light (Ultra Light)
 * font-weight: 300; // Light
 * font-weight: 400; // Normal
 * font-weight: 500; // Medium
 * font-weight: 600; // Semi Bold (Demi Bold)
 * font-weight: 700; // Bold
 * font-weight: 800; // Extra Bold (Ultra Bold)
 * font-weight: 900; // Black (Heavy)
 */

//https://github.com/webpack-contrib/less-loader/issues/67
//https://github.com/madskristensen/BundlerMinifier/issues/191

// DIN PRO (converted)
@font-face {
    font-family: "DINPro";
    src: url('#{$converted-font-path}/DINPro-Light.eot');
    src: url('#{$converted-font-path}/DINPro-Light.eot?#iefix') format('embedded-opentype'), url('#{$converted-font-path}/DINPro-Light.woff2') format('woff2'), url('#{$converted-font-path}/DINPro-Light.woff') format('woff'), url('#{$converted-font-path}/DINPro-Light.ttf') format('truetype'), url('#{$converted-font-path}/DINPro-Light.svg#DINPro-Regular') format('svg');
    font-weight: 300;
    font-style: normal;
}

@font-face {
    font-family: "DINPro";
    src: url('#{$converted-font-path}/DINPro-Regular.eot');
    src: url('#{$converted-font-path}/DINPro-Regular.eot?#iefix') format('embedded-opentype'), url('#{$converted-font-path}/DINPro-Regular.woff2') format('woff2'), url('#{$converted-font-path}/DINPro-Regular.woff') format('woff'), url('#{$converted-font-path}/DINPro-Regular.ttf') format('truetype'), url('#{$converted-font-path}/DINPro-Regular.svg#DINPro-Regular') format('svg');
    font-weight: 400;
    font-style: normal;
}

@font-face {
    font-family: "DINPro";
    src: url('#{$converted-font-path}/DINPro-Medium.eot');
    src: url('#{$converted-font-path}/DINPro-Medium.eot?#iefix') format('embedded-opentype'), url('#{$converted-font-path}/DINPro-Medium.woff2') format('woff2'), url('#{$converted-font-path}/DINPro-Medium.woff') format('woff'), url('#{$converted-font-path}/DINPro-Medium.ttf') format('truetype'), url('#{$converted-font-path}/DINPro-Medium.svg#DINPro-Medium') format('svg');
    font-weight: 500;
    font-style: normal;
}

@font-face {
    font-family: "DINPro";
    src: url('#{$converted-font-path}/DINPro-MediumItalic.eot');
    src: url('#{$converted-font-path}/DINPro-MediumItalic.eot?#iefix') format('embedded-opentype'), url('#{$converted-font-path}/DINPro-MediumItalic.woff2') format('woff2'), url('#{$converted-font-path}/DINPro-MediumItalic.woff') format('woff'), url('#{$converted-font-path}/DINPro-MediumItalic.ttf') format('truetype'), url('#{$converted-font-path}/DINPro-MediumItalic.svg#DINPro-MediumItalic') format('svg');
    font-weight: 500;
    font-style: italic;
}

@font-face {
    font-family: "DINPro";
    src: url('#{$converted-font-path}/DINPro-Bold.eot');
    src: url('#{$converted-font-path}/DINPro-Bold.eot?#iefix') format('embedded-opentype'), url('#{$converted-font-path}/DINPro-Bold.woff2') format('woff2'), url('#{$converted-font-path}/DINPro-Bold.woff') format('woff'), url('#{$converted-font-path}/DINPro-Bold.ttf') format('truetype'), url('#{$converted-font-path}/DINPro-Bold.svg#DINPro-Bold') format('svg');
    font-weight: 700;
    font-style: normal;
}

// HELVETICA ROUNDED
@font-face {
    font-family: "Helvetica";
    src: url('#{$cond-font-path}/HelveticaRounded-BoldCond.eot');
    src: url('#{$cond-font-path}/HelveticaRounded-BoldCond.eot?#iefix') format('embedded-opentype'), url('#{$cond-font-path}/HelveticaRounded-BoldCond.woff2') format('woff2'), url('#{$cond-font-path}/HelveticaRounded-BoldCond.woff') format('woff'), url('#{$cond-font-path}/HelveticaRounded-BoldCond.ttf') format('truetype'), url('#{$cond-font-path}/HelveticaRounded-BoldCond.svg#HelveticaRounded-BoldCond') format('svg');
    font-weight: 700;
    font-stretch: condensed;
    font-style: normal;
}

// ICON
@font-face {
    font-family: $ico-font-family;
    src: url('#{$ico-font-path}/#{$ico-font-family}.eot?v=#{$ico-font-version}');
    src: 
    //url('#{$ico-font-path}/WHN-Icons.eot?#iefix&v=#{$ico-font-version}') format('embedded-opentype'),
    url('#{$ico-font-path}/#{$ico-font-family}.woff?v=#{$ico-font-version}') format('woff'),
    url('#{$ico-font-path}/#{$ico-font-family}.ttf?v=#{$ico-font-version}') format('truetype'),
    url('#{$ico-font-path}/#{$ico-font-family}.svg?v=#{$ico-font-version}#fontawesomeregular') format('svg');
    font-weight: normal;
    font-style: normal;
}

Notice the first line how I use a custom variables file. This is simply a copy of Bootstrap's _variables.scss. I re-create the setup in my own scss file and override the variables file.

The most important reason for using a separate file is because fonts render block while loading. Separating assets (fonts, artwork, images, ...) allows me to use async CSS <link /> to eliminate render blocking issues.

The custom bootstrap setup projectname.atomic.scss:

/*!
 * Atomic design principle
 */

@import "../vendor/bootstrap/scss/functions";
@import "_variables/fa.custom.variables"; // font awesome override variables
@import "_variables/projectname.custom.variables"; // bootstrap override variables
@import "../vendor/bootstrap/scss/variables";
@import "../vendor/bootstrap/scss/mixins";
@import "_mixins/company.custom.mixins"; // custom mixins
@import "../vendor/bootstrap/scss/root";
@import "../vendor/bootstrap/scss/reboot";
@import "../vendor/bootstrap/scss/type";
@import "../vendor/bootstrap/scss/images";
@import "../vendor/bootstrap/scss/code";
@import "../vendor/bootstrap/scss/grid";
@import "../vendor/bootstrap/scss/tables";
@import "../vendor/bootstrap/scss/forms";
@import "../vendor/bootstrap/scss/buttons";
@import "../vendor/bootstrap/scss/transitions";
@import "../vendor/bootstrap/scss/dropdown";
@import "../vendor/bootstrap/scss/button-group";
@import "../vendor/bootstrap/scss/input-group";
@import "../vendor/bootstrap/scss/custom-forms";
@import "../vendor/bootstrap/scss/nav";
@import "../vendor/bootstrap/scss/navbar";
@import "../vendor/bootstrap/scss/card";
@import "../vendor/bootstrap/scss/breadcrumb";
@import "../vendor/bootstrap/scss/pagination";
@import "../vendor/bootstrap/scss/badge";
@import "../vendor/bootstrap/scss/jumbotron";
@import "../vendor/bootstrap/scss/alert";
@import "../vendor/bootstrap/scss/progress";
@import "../vendor/bootstrap/scss/media";
@import "../vendor/bootstrap/scss/list-group";
@import "../vendor/bootstrap/scss/close";
@import "../vendor/bootstrap/scss/modal";
@import "../vendor/bootstrap/scss/tooltip";
@import "../vendor/bootstrap/scss/popover";
@import "../vendor/bootstrap/scss/carousel";
@import "../vendor/bootstrap/scss/utilities";
@import "../vendor/bootstrap/scss/print";

// This is where my real work comes in, for big projects this has proven to be succesful in maintaining and multisite setup
@import "atoms/atoms";
@import "molecules/molecules";
@import "organisms/organisms";
@import "tenants/projectname/themes/themes";
@import "tenants/projectname/pages/pages";

By re-creating the setup, it becomes easy to override the default. Just be aware of the !default usage when you're inserting your own variables and not use !default when it's a bootstrap variable.

A piece of my _projectname.custom.variables.scss

$design-path: "/Design" !default;
$font-family-headings: Helvetica, sans-serif !default;
$font-family-headings-fallback: DINPro, sans-serif !default;

$cond-font-path: "../../../fonts/app/helvetica-rounded-converted" !default;
$pro-font-path: "" !default;
$converted-font-path: "../../../fonts/app/din-pro-converted" !default;
$ico-font-path: "../../../fonts/app/icomoon/projectname" !default;
$ico-font-version: "xyftn5" !default;
$ico-font-family: 'icomoon' !default;

$theme-green: #7EC796 !default; // de york
$theme-yellow: #FFBD51 !default; // texas rose
$theme-red: #E24B43 !default; // cinnabar
$theme-blue: #7CB1D1 !default; // half baked
$theme-pink: #FE8980 !default; // vivid tangerine
$theme-cyan: #A5B54E !default; // olive green
$theme-indigo: #12884E !default; // salem
$theme-orange: #F77D54 !default; // coral
$theme-teal: #307A83 !default; // paradiso

// FONTAWESOME VARIABLES override
$fa-var-arrow-down: "\e903";
$fa-var-arrow-left: "\e900";
$fa-var-arrow-right: "\e901";
$fa-var-arrow-up: "\e902";
$fa-var-bars: "\e914";
$fa-var-calendar: "\e908";
$fa-var-chevron-down: "\e907";
$fa-var-chevron-left: "\e904";
$fa-var-chevron-up: "\e906";
$fa-var-chevron-right: "\e905";

// BOOTSTRAP VARIABLES override
$grid-breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px );
$grid-gutter-width: 2rem;
$grid-gutter-fluid-width: 2.1875rem !default; // custom variable
$container-max-widths: ( sm: 540px, md: 720px, lg: 960px, xl: 1140px, xxl: 1340px );

// and many many more become available at your fingertips

What is the correct way to import the Bootstrap functions I want to override?

This should be covered in the previous question. Simply create your own _functions.scss and add it below the original one.

@import "../vendor/bootstrap/scss/functions";
@import "_functions/company.custom.functions"; // override or extend, your choice

I realise this is a lot to take in, but you asked quite a lot :) Seems like you got the hang of it already or just some pieces here and there. Feel free to ask more info, like the Gulpfile.js or something.

Good luck and have fun digesting one of my longer answers ^^

like image 122
Tim Vermaelen Avatar answered Oct 12 '22 20:10

Tim Vermaelen