Sass & Overriding Bootstrap Variables

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 [email protected]^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!

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