Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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!

Tip

Instead of node-sass, install sass. It's a pure-JavaScript implementation that is easier to install and is now recommended.

yarn add sass-loader@^7.0.1 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!

25
Login or Register to join the conversation
Default user avatar

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? :)

1 Reply

Hey Evgeny,

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

Cheers!

1 Reply
John christensen Avatar
John christensen Avatar John christensen | posted 2 years ago

I had great difficulty getting node-sass to install without errors (Nov 2020; Symfony 4.2.5, webpack-encore-bundle 1.8.0, yarn 1.22.4, npm 6.14.8). The sass-loader installed ok, but the node-sass command kept giving me: gyp ERR! stack Error: `make` failed with exit code: 2
(and sometimes error code 1).

After much travail, I finally got it to work by manually adding the following two devDependencies to package.json:

"sass-loader": "^9.0.1",
"node-sass": "^4.5.0"

and then running yarn install

Hope this helps someone else :)

John

Reply

Hey John christensen!

Ah, sorry about the trouble! Ok, so the short story is that node-sass is kind of a pain in the butt. Whenever new operating systems comes out, you need to upgrade to a new version of node-sass, otherwise it won't build. So, our code download gets quickly out of date.

The solution (and I've just updated the "finished" code download to reflect this and we'll soon add a note to the video and script) is to install "sass" instead of node-sass. It's a drop-in replacement written entirely in JavaScript. So, the command to install would be something like:


yarn add sass-loader sass --dev

Anyways, sorry you hit the issue, but I appreciate the comment - it bumped me to get the out of our tutorial to make life easier.

Cheers!

Reply

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

Reply

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!

Reply

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...

Reply

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!

Reply

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.

Reply

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!

1 Reply

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!

Reply

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.

Reply

Hey Julien,

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

Cheers!

Reply

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!

Reply

Hey Julien,

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

Cheers!

Reply
Akavir S. Avatar
Akavir S. Avatar Akavir S. | posted 2 years ago

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';

Reply

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';

1 Reply

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!

Reply
Default user avatar
Default user avatar Anton Bagdatyev | posted 2 years ago

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

Reply

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!

Reply
Default user avatar
Default user avatar Anton Bagdatyev | weaverryan | posted 2 years ago

Just checked the Bootstrap SCSS sources:

$info: $cyan !default;

You rock man!

Reply
Default user avatar
Default user avatar Anton Bagdatyev | posted 2 years ago

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!

Reply

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!

Reply

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.

Reply

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works great with Symfony5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.91.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.3.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.5
        "symfony/console": "^4.0", // v4.2.5
        "symfony/flex": "^1.9", // v1.17.6
        "symfony/form": "^4.0", // v4.2.5
        "symfony/framework-bundle": "^4.0", // v4.2.5
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.5
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.5
        "symfony/validator": "^4.0", // v4.2.5
        "symfony/web-server-bundle": "^4.0", // v4.2.5
        "symfony/webpack-encore-bundle": "^1.4", // v1.5.0
        "symfony/yaml": "^4.0", // v4.2.5
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.5
        "symfony/dotenv": "^4.0", // v4.2.5
        "symfony/maker-bundle": "^1.0", // v1.11.5
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.5
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.27.0", // 0.27.0
        "autocomplete.js": "^0.36.0",
        "autoprefixer": "^9.5.1", // 9.5.1
        "bootstrap": "^4.3.1", // 4.3.1
        "core-js": "^3.0.0", // 3.0.1
        "dropzone": "^5.5.1", // 5.5.1
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.4.0", // 3.4.0
        "popper.js": "^1.15.0",
        "postcss-loader": "^3.0.0", // 3.0.0
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^7.0.1", // 7.3.1
        "sortablejs": "^1.8.4", // 1.8.4
        "webpack-notifier": "^1.6.0" // 1.7.0
    }
}