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
:
// ... 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()
.
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? :)