Sass & Overriding Bootstrap Variables

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

What if I want to use Sass instead of normal CSS, or maybe Less or Stylus? Normally, that takes some setup: you need to create a system that can compile all of your Sass files into CSS. But with Encore, we get this for free!

Rename app.css to app.scss. Of course, when we do that, the build fails because we need to update the import in app.js:

26 lines assets/js/app.js
... lines 1 - 7
// any CSS you require will output into a single css file (app.css in this case)
import '../css/app.scss';
... lines 10 - 26

But the build still fails. Go check out the error. Woh! That's awesome! It basically says:

Hey! How are you? Great weather lately, right? Listen, it looks like you're trying to load a Sass file. That's super! To do that, enable the feature in Encore and install these libraries.

This is the philosophy of Encore: give you a really solid, but small-ish core, and then offer a ton of optional features.

Enabling Sass

Go back to webpack.config.js. The enableSassLoader() line is already here. Uncomment it:

... lines 1 - 2
Encore
... lines 4 - 50
// enables Sass/SCSS support
.enableSassLoader()
... lines 53 - 66
;
... lines 68 - 69

Back at the terminal, copy the yarn add command, go to the open tab, and run it!

yarn add sass-loader@^7.0.1 node-sass --dev

This could take a minute or two: node-sass is a C library and it may need to compile itself depending on your system. Ding!

Thanks to the watch script, we normally don't need to worry about stopping or restarting Encore. There is one notable exception: when you make a change to webpack.config.js, you must stop and restart Encore. That's just a limitation of Webpack itself: it can't re-read the fresh configuration until you restart.

Hit Control+C and then run yarn watch again.

yarn watch

And this time... yes! We just added Sass support in like... two minutes - how awesome is that?

Organizing into Partials

This next part is optional, but I want to get organized... instead of having one big file, create a new directory called layout/. And for this top stuff, create a file called _header.scss. Little-by-little, we're going to move all of this code into different files. Grab the first section and put it into header:

body {
position: relative;
background: #efefee;
min-height: 45rem;
padding-bottom: 80px;
}
html {height:100%}
/* NAVIGATION */
.navbar-bg {
background: url('../images/space-nav.jpg');
background-size: 80%;
}
.dropdown-menu, .dropdown-menu.show {
right: 0;
}
.space-brand {
color: #fff;
font-weight: bold;
}
.nav-profile-img {
width: 50px;
border: 1px solid #fff;
}
.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover {
color: #efefee;
}

We'll import the new files when we finish.

Next is the "advertisement" CSS. Create another folder called components/. And inside, a new _ad.scss file. I'll delete the header... then move the code there:

.ad-space {
background: #fff;
border-radius: 5px;
border-top: 5px solid green;
}
.advertisement-img {
width: 150px;
height: auto;
border: 2px solid #efefee;
border-radius: 5px;
}
.advertisement-text {
font-weight: bold;
}
.quote-space {
background: #fff;
margin-top: 30px;
border-radius: 5px;
border-top: 5px solid hotpink;
}

Let's keep going! For the article stuff, create _articles.scss, and move the code:

.main-article {
border: 2px solid #efefee;
Background: #fff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.main-article img {
width: 100%;
height: 250px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
border-top: 5px solid lightblue;
}
.article-container {
border: 1px solid #efefee;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background: #fff;
}
.main-article-link, .article-container a {
text-decoration: none;
color: #000;
}
.main-article-link:hover {
text-decoration: none;
color: #000;
}
.article-title {
min-width: 300px;
}
@media (max-width: 440px) {
.article-title {
min-width: 100px;
max-width: 245px;
}
}
.article-img {
height: 100px;
width: 100px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.article-author-img {
height: 25px;
border: 1px solid darkgray;
}
.article-details {
font-size: .8em;
}
... lines 60 - 140

Then, _profile.scss, copy that code... and paste:

.profile-img {
width: 150px;
height: auto;
border: 2px solid #fff;
}
.profile-name {
font-size: 1.5em;
}
.my-article-container {
background: #FFBC49;
border: solid 1px #efefee;
border-radius: 5px;
}

For the "Create Article" and "Article Show" sections, let's copy all of that and put it into _article.scss:

.main-article {
border: 2px solid #efefee;
Background: #fff;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.main-article img {
width: 100%;
height: 250px;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
border-top: 5px solid lightblue;
}
.article-container {
border: 1px solid #efefee;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
background: #fff;
}
.main-article-link, .article-container a {
text-decoration: none;
color: #000;
}
.main-article-link:hover {
text-decoration: none;
color: #000;
}
.article-title {
min-width: 300px;
}
@media (max-width: 440px) {
.article-title {
min-width: 100px;
max-width: 245px;
}
}
.article-img {
height: 100px;
width: 100px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.article-author-img {
height: 25px;
border: 1px solid darkgray;
}
.article-details {
font-size: .8em;
}
/* CREATE ARTICLE */
.create-article-container {
min-width: 400px;
background-color: lightblue;
border-radius: 5px;
}
/* ARTICLE SHOW PAGE */
.show-article-container {
width: 100%;
background-color: #fff;
}
.show-article-container.show-article-container-border-green {
border-top: 3px solid green;
border-radius: 3px;
}
.show-article-img {
width: 250px;
height: auto;
border-radius: 5px;
}
.show-article-title {
font-size: 2em;
}
.like-article, .like-article:hover {
color: red;
text-decoration: none;
}
@media (max-width: 991px) {
.show-article-title {
font-size: 1.5em;
}
.show-article-title-container {
max-width: 220px;
}
}
.article-text {
margin-top: 20px;
}
.share-icons i {
font-size: 1.5em;
}
.comment-container {
max-width: 600px;
}
.comment-img {
width: 50px;
height: auto;
border: 1px solid darkgray;
}
.commenter-name {
font-weight: bold;
}
.comment-form {
min-width: 500px;
}
@media (max-width: 767px) {
.comment-form {
min-width: 260px;
}
.comment-container {
max-width: 280px;
}
}

And for the footer, inside layout/, create one more file there called _footer.scss and... move the footer code:

.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
line-height: 60px; /* Vertically center the text there */
background-color: #fff;
margin-top: 10px;
}

And finally, copy the sortable code, create another components partial called _sortable.scss and paste:

.sortable-ghost {
background-color: lightblue;
}
.drag-handle {
cursor: grab;
}

Now we can import all of this with @import './layout/header' and @import './layout/footer':

@import '~bootstrap';
@import '~font-awesome';
@import './layout/header';
@import './layout/footer';
... lines 6 - 11

Notice: you don't need the _ or the .scss parts: that's a Sass thing. Let's add a few more imports for the components: ad, articles, profile and sortable:

... lines 1 - 3
@import './layout/header';
@import './layout/footer';
@import './components/ad';
@import './components/articles';
@import './components/profile';
@import './components/sortable';

Phew! That took some work, but I like the result! But, of course, Encore is here to ruin our party with a build failure:

Cannot resolve ./images/space-nav.jpeg

We know that error! In _header.scss... ah, there it is:

... lines 1 - 11
.navbar-bg {
background: url('../images/space-nav.jpg');
... line 14
}
... lines 16 - 34

The path needs to go up one more level now:

... lines 1 - 11
.navbar-bg {
background: url('../../images/space-nav.jpg');
... line 14
}
... lines 16 - 34

And... it works.

Move over and make sure nothing looks weird. Brilliant!

Adding Variables

To celebrate that we're processing through Sass, let's at least use one of its features. Create a new directory called helper/ and a new file called _variables.scss.

At the top of _header.scss, we have a gray background color:

body {
... line 2
background: #efefee;
... lines 4 - 5
}
... lines 7 - 34

Just to prove we can do it, in _variables, create a new variable called $lightgray set to #efefee:

$lightgray: #efefee;

And back in headers, reference that: $lightgray:

body {
... line 2
background: $lightgray;
... lines 4 - 5
}
... lines 7 - 34

We even get auto-completion on that! As soon as we save, the build fails!

Undefined variable: "$lightgray"

Perfect! Because... inside of app.scss, all the way on top, we still need to @import the helper/variables file:

@import './helper/variables';
... lines 2 - 13

About a second later... ding! It builds and... the background is still there.

Overriding Bootstrap Sass Variables

But wait, there's more! When we import bootstrap, Encore has some logic to find the right CSS file in that package. But now that we're inside a Sass file, it's smart enough to instead import the bootstrap.scss file! Woh!

Check it out. Hold Command or Ctrl and click ~bootstrap to jump to that directory. Then open up package.json. This has a style key, but it also has a sass key! Because we're importing from inside a Sass file, Encore first looks for the sass key and loads that file. If there isn't a sass key, it falls back to using style.

Now look at the font-awesome/ directory and find its package.json file. It actually does not have a sass key! And so, it's still loading the font-awesome.css file, which is fine. If you did want to load the Sass file, you would just need to point at the file path directly.

Anyways, to prove that the Bootstrap Sass file is being loaded, we can override some of its variables. See this search button? It's blue because it has the btn-info class. It's color hash is... here: #1782b8.

Suppose you want to change the info color globally to be a bit darker. Bootstrap lets you do that in Sass by overriding a variable called $info.

Try it: inside the variables file, set $info: to darken(), the hash, and 10%:

$info: darken(#17a2b8, 10%);
... lines 2 - 5

Once the build finishes... watch closely. It got darker! How cool is that?

Next, let's fix our broken img tags thanks to one of my favorite new Encore features called copyFiles().

Leave a comment!

  • 2020-07-31 Victor Bocharsky

    Hey Julien,

    Great! Fairly speaking, if it didn't work - I would be surprised :) Anyway, I'm glad this workaround works for you!

    Cheers!

  • 2020-07-30 Julien Bonnier

    Yeah, I thought I tried that already it didn't work thus ending up with that extra /scss but apparently I was wrong. It does work without it.

  • 2020-07-29 Victor Bocharsky

    Oh, and one more suggestion... I think you can simplify your path to:

    $custom-select-indicator: url(~bootstrap/../../assets/images/custom-select.png);

    I.e. in theory, you don't need to go inside scss/ dir and then go back... but that's just in theory, I won't be surprised too much if you say that it does not work this way :)

    Cheers!

  • 2020-07-29 Victor Bocharsky

    Hey Julien,

    Woh, thanks for sharing this solution with others! We do talk about "~" here: https://symfonycasts.com/sc... - this helps to point to the files that are in node_modules/ directory. But I've never heard that it might help in this case too, so good catch! So yeah, it looks weird to me too fairly speaking, but if it works as you need - great, that's really nice workaround I think :)

    Cheers!

  • 2020-07-29 Victor Bocharsky

    Hey Julien,

    Ah, I see... yeah, this complicates things a bit.

    Cheers!

  • 2020-07-29 Julien Bonnier

    Holly smoke! I can't believe it. I found a working solution.

    It's kind of an ugly (or least weird) hack. But it works.

    Since that variable is only used in Bootstrap but the image is in my assets folder, well I'm "hacking" my way into and... It works!

    $custom-select-indicator: url(~bootstrap/scss/../../../assets/images/custom-select.png);

    Cheers!

  • 2020-07-28 Julien Bonnier

    Well, that's my problem... This variable is only used in Bootstrap (which is in node_modules)... I'm overriding Bootstrap default with that one. So the path I'm using is the one relative to my file, but once Bootstrap uses it Webpack explodes. I will keep digging but so far I don't have a suitable solution.

  • 2020-07-28 Victor Bocharsky

    Hey Julien,

    Hm, this sounds like the path was resolved first, and then the already resolved path was passed to different spots, but looks like for some spots it should be different? I'd recommend you to comment out all places where you use that $custom-select-indicator var except one place, and try to get it working for that spot. When it's done, uncomment one more line and check if it still works. Probably path will be different for different files where you use that var... Really not sure how rewriting paths works with variables. Also, you can try to start the path with "./", e.g:
    $custom-select-indicator: url('./../../assets/images/custom-select.png') !default;

    It should work the same was as starting with "../", but just in case will be good to check I think.

    I hope this helps!

    Cheers!

  • 2020-07-27 Julien Bonnier

    No it does not help... I really don't understand how it resolves the path.

    See tests and results which really don't make sense to me.

    ---------------------------------------------------

    Test 1
    $custom-select-indicator: url('../../images/custom-select.png') !default;

    Result
    Syntax Error: ModuleNotFoundError: Module not found: Error: Can't resolve '../../node_modules/bootstrap/images/custom-select.png' in '/home/julien/dev/project/symfony/assets/css'

    ---------------------------------------------------

    Test 2
    $custom-select-indicator: url('../../../images/custom-select.png') !default;

    Result
    Syntax Error: ModuleNotFoundError: Module not found: Error: Can't resolve '../../images/custom-select.png' in '/home/julien/dev/project/symfony/assets/css'

    node_modules and bootstrap are gone from the path

    ---------------------------------------------------

    Test 3
    $custom-select-indicator: url('../../../../images/custom-select.png') !default;

    Result
    Syntax Error: ModuleNotFoundError: Module not found: Error: Can't resolve '../../../images/custom-select.png' in '/home/julien/dev/project/symfony/assets/css'

    node_modules and bootstrap are gone from the path and extra ../ is added

    ---------------------------------------------------

    In theory if the path where it looks for the image is relative to '/home/julien/dev/project/symfony/assets/css' then Test 2 should be very close to what I'm looking for but I would need to add assets/ to my path. So let's try it

    Test 4
    $custom-select-indicator: url('../../../assets/images/custom-select.png') !default;

    Result
    Syntax Error: ModuleNotFoundError: Module not found: Error: Can't resolve '../../node_modules/assets/images/custom-select.png' in '/home/julien/dev/project/symfony/assets/css'

    node_modules is back but bootstrap is still gone from the path.

    What the...?

    I'm kinda lost here...

  • 2020-07-27 Victor Bocharsky

    Hey Julien,

    Hm, first of all, I suppose you're missing quotes in the path, did you try:
    $custom-select-indicator: url("../../images/custom-select.png") !default;

    Does it help? Or do you see the exact same error?

    Cheers!

  • 2020-07-27 Vladimir Sadicov

    Hey Julien Bonnier

    That's correct! Thank you for replying on this thread, sometimes happens that we can miss comments (it's not too often) and it's awesome that users help each other!

    Cheers! Have a nice day!

  • 2020-07-26 Julien Bonnier

    Hello World!

    I'm trying to override Bootstrap's variable $custom-select-indicator to change the url().

    I can't seem to find how to refer to that image in my scss file.

    My directory structure is the following.


    assets
    ├── css
    │ ├── component
    │ │ ├── theme-colors
    │ │ └── widgets
    │ ├── core
    │ │ ├── animation
    │ │ ├── breadcrumb
    │ │ ├── buttons
    │ │ ├── extra
    │ │ ├── layout
    │ │ ├── loader
    │ │ ├── scafholdings
    │ │ ├── sidebar
    │ │ ├── topbar
    │ │ └── wave-effects
    │ ├── helper
    │ │ └── mixins
    │ └── lib
    ├── images
    │ └── users
    └── js
    └── pages
    └── dashboards

    My variable file is in assets/css/helper and my image is in assets/images.

    So in my _variables.scss I refer to it like this.

    $custom-select-indicator: url(../../images/custom-select.png) !default;

    But when I compile I get.

    Syntax Error: ModuleNotFoundError: Module not found: Error: Can't resolve '../../node_modules/bootstrap/images/custom-select.png' in '/home/julien/dev/project/symfony/assets/css'

    Can any one help me with that?

    Thanks,

    Julien

  • 2020-07-26 Julien Bonnier

    Hi Virgile,

    If you are trying to override bootstrap variables, you'll have to import bootstrap SCSS file. You are importing a compiled css file, thus it is not going to work.

    Try with

    @import '~bootstrap/scss/bootstrap';

  • 2020-04-28 virgile sahaguian

    Hello,
    Looks like overriding the $info variable is not working for me. (the $lightgray worked correctly)
    here is my variables file

    $info: darken(#17a2b8, 10%);
    $lightgray:red;

    here is my app.scss

    @import './helper/_variables.scss';
    @import '~bootstrap/dist/css/bootstrap.css';
    @import '~font-awesome';
    @import '/layout/_footer.scss';
    @import '/layout/_header.scss';
    @import '/components/_ad.scss';
    @import '/components/_article.scss';
    @import '/components/_profile.scss';
    @import '/components/_sortable.scss';

  • 2020-03-16 Anton Bagdatyev

    Just checked the Bootstrap SCSS sources:

    $info: $cyan !default;

    You rock man!

  • 2020-03-16 weaverryan

    Hey @Anton!

    SUCH a good question - because the order doesn’t make any sense, right? This works because of a special Sasa feature. In bootstrap, when those variables are set, they are set with a special flag that says “only use this value if nobody else ever sets this variable”. THAT is why it works. Otherwise, you’re right, bootstrap would override *our* values :).

    Cheers!

  • 2020-03-16 weaverryan

    Hey @Anton!

    Yep - I’m using phpstorm! The whole ~ thing to refer to css files in the node_modules directory is, I believe, a special thing that Webpack does. Phpstorm is aware of this Webpack trick, but apparently VSCode is not. If you can manually configure Webpack aliases in VSCode, you might be able to map ~ to node_modules... but if that doesn’t work, I’m afraid that there won’t be much you can do (but I could be wrong!).

    Let me know if you find anything out!

    Cheers!

  • 2020-03-16 Anton Bagdatyev

    How does `$info: darken(...)` in helper/_variables.scss override the bootstrap's one if `@import './helper/variables'` is imported before `@import '~bootstrap'`? Thank you!

  • 2020-03-16 Anton Bagdatyev

    Are you using PHPStorm? I'm using VS Code, and there Command/Ctrl + click trick on `~bootstrap` to jump to the node_modules/bootstrap dir doesn't seem to work in VS Code. Maybe you know if there is a way to enable it? Thanks!

  • 2019-06-11 Victor Bocharsky

    Hey Evgeny,

    Good question! :) The answer is in "!default" flag, see the docs for more context: https://getbootstrap.com/do...

    Cheers!

  • 2019-06-10 Evgeny

    Hello!
    Could you please explain me one thing that I can't understand.
    In assets/css/app.scss we firstly import _variables.scss, where we overload the bootsrap's $info variable, and after that we import ~bootstrap itself, where the $info is declared.
    So we managed to overload the variable which hasn't been declared yet. How did we do that? :)

  • 2019-04-29 Victor Bocharsky

    Hey Stephane,

    Nice catch! Yeah, that was a misprint, it should be `@import "./helper/variables"`, you can check it with related code blocks below the video: https://symfonycasts.com/sc... . Unfortunately, that part in the video was not re-recorded, but we fixed it in the code.

    But actually, looks like it works both ways, so it's kinda matter of preference, but we do recommend to prefix with "./" in such cases.

    Cheers!

  • 2019-04-26 Stéphane

    Hey Ryan,
    Nice tuto. Thank.

    Why when you call variables.scss, you write @import "helper/variables"; while when you call others scss you write @import "./layout/header";
    I realise when I clean ./ it's working.