With Style

Last Updated:

Tags: technology, webpage, development, SCSS

4 in a series of 5 articles in the Build a Website series.

Style!

In a previous article we set up Hugo, and got it running locally. If you didn’t do any styling, or download a Hugo theme, then your site probably looks pretty sad right now. In this article, I’ll go through how you can get SCSS and Material up and running for your Hugo site.

To fix that you gotta tell the page how everything should look, and the way to do that is CSS. If you have ever worked on a project that’s significantly large, you’ll probably resonate with the thought that every new thing you have to work on and maintain takes a lot of time. You also want your website to look snazzy (at least usually you do right?), but keeping up with the latest trends can be it’s own chore. CSS/SASS Frameworks fill that gap. I’ll show how I’ve set up my CSS pipeline and how I try to organize it (and why).

I’m going to work off the assumption that you know how CSS works. There’s some pretty good resources out there for getting your feet wet. My favorite documentation for CSS and other web tech is MDN. It’s a solid resource not just for CSS, but JavaScript, and HTML. If you don’t have any experience with CSS, then you’ll probably have trouble with the later SASS-y stuff. Check out this page.

I mentioned in getting started that I used SCSS, and Material.io, so let’s dive in and see what we need to get those working.

A Word About CSS Frameworks

Let’s address the elephant in the room: Bootstrap. Any web developer worth their salt will know about Bootstrap. It’s been one of the most popular style frameworks for a while now. It really is a framework; Twitter’s Bootstrap not only gives you styling elements to work with, things like theming tie-ins and layout standards, it also gives you distinct and dynamic elements. Many of the elements from bootstrap require JavaScript, notoriously it needs jQuery (until 5.0 is released), so if you don’t want to include vendor JavaScript, Bootstrap might not be for you.

With most of these big libraries, you are technically able to break out pieces from them and only take the parts that you like. But like Frankenstein’s monster, the end result is a menagerie of pieces cobbled together. While it might be beautiful, you will have a monster. I mean the book version of Frankenstein for this allegory, not the movies.

Anyway, I usually find that if I’m picking and choosing which parts of a framework I want, then what invariably happens is I spend a lot of time fine-turning and debugging it. In the end, I usually am taking along more cargo than I was expecting. For things like Bootstrap or Bootstrap sized, often you can source the whole library from CDN’s (Content Delivery Networks) or links outside your domain. This can reduce the load from your host (in general a good thing). This comes with the side-effect of a dependency. Whether this side-effect is detrimental or not will more than likely be the topic of another article.

I tend to use frameworks out of convenience. If your job title says “Designer,” or something like that, you may not need to, or you may not want to, or you may not be allowed to. The big benefit & drawback to frameworks is their consistency: either the framework works perfectly out of the box, or you need to hope that the framework lets you modify the thing you’re working on.

My advice is this: either work with a style framework, or don’t use one. Don’t try and wiggle your way into a framework to get the parts you want. And I mean that all the way: this is a slippery slope argument, but it’s a pet peeve of mine. I’ve found myself working around the framework and building out custom classes to cover for parts of the framework, and trying to make it look cohesive. That’s when I step back and say “Wow I really wish I wasn’t doing this.” That said, I rarely listen to my advice.

SASS

SASS is a preprocessor, which means it takes good-ol' CSS and extends it using a tool that acts like a compiler. SASS has been around for a while now, and while CSS has modernized a little, SASS is still an integral part of many web stacks. If you’re using Material like me, then it’ll be part of your pipeline as well.

If you’re not using SASS then what you’re doing when you’re running your site is building a complete CSS sheet that specifies your whole site’s styling. With modern CSS you’re more likely to modularize that, but back in the day, you were writing this huge file all by hand while smoking a pack of cigarettes inside, and watching the newest Tom and Jerry Cartoon on your CRT television. Ok, so maybe this way of working isn’t that old, but it really feels like it when you install some modern tooling.

Check out getting it installed here. Pick your poison, as webpack doesn’t care much about Dart vs JavaScript SASS.

Material

I’m using Material, whose branding says

Material Components for Android, iOS, web and Flutter help developers execute Material Design with modular and customizable UI components. Developed by a core team of engineers and UX designers at Google, these components enable a reliable development workflow to build beautiful and functional user experiences.

Notice that Material is a ‘components’ package. It tries to not impose too much styling dogma, where Bootstrap really tends to lock you in (at least it feels that way). Again, this is a good or bad thing depending on a lot of factors. I’ll just show you the barest of bones way to get Material working, and it won’t be pretty. This also won’t make the system lean - you’ll need to work on that yourself, and I’ll try to cover both of those topics in a later article.

Material components, do restrict you in some of the ways that other CSS frameworks limit you in. Importantly, if you do want to use specific components there are particular pieces that you need to have. I learned this not by reading the documentation but by finding a bug during what I call a “deep-dive.” I will be dissecting how I figured this out in another article, but the short of it is if you want your tabs to look correct you need every part of that component, and I did not find the documentation to help out too much.

So that aside how do you get Material? If you’re a DIY kinda person, then readup and start cookin!. You can use a CDN if you want to go the recommended route. If you are like me, you like to package your site and get into the nitty-gritty. In that case, “install locally.”

npm i material-components-web

If that’s gibberish to you, look up “npm” and Node.js, I’m sure I’ll have more to say about it in another article.

The File System and You

Directory structure is going to be important to maintaining your own Hugo website. What should yours look like? Before we dive right in, I’ll make a few statements. First, whatever directory you created your website in is the “root” directory. If you’re curious, for me it’s “C:\Users\Jon\Documents\Dev\websites\yockyard”. Instead of me including that junk in everything I talk about, just assume that all the paths are relative to that root directory, unless you I say otherwise.

Let’s (briefly) go over the Hugo directories.

Hugo Built-in Directories

Rapid fire time:

  • Archetypes
  • content
  • data
  • layouts
  • static
  • themes

All of these directories come out of the box and all of these are on the root for a Hugo site. What does it all mean?

Archetypes: When you’re in the groove of writing articles for your website, you’ll use what is called “Archetypes” which are basically templates. For us getting our styling done, we don’t really need to worry about it.

Content: This is where all your stuff lives. For a basic site you’ll have something like a ‘posts’ sub-directory. As your site grows you may have other base types. For my site (currently) I also have ‘Series’ section where I have summaries for series that I called out (like this one!). Read up more on this feature here. I won’t dive too deep on this topic. For blogging “posts” should work fine.

Data: The data folder is sometimes important. It’s where you keep all the info for things that aren’t templates. In my setup, I only have the manifest.json file that webpack generates. This is a flexible folder, so you will probably find a way to use it.

Layouts: This is where the heavy-lifting machinery starts working. My last article went over how this works. A few sub-directories are important, and some are dependent on which content types you’re using. If you elected to use a Hugo Theme, then Hugo will use those layouts in any gaps you may have. You can overwrite those layouts by writing your own.

If you’re rolling your own, I suggest you read up from the Hugo documentation and my articles of course. If you’re following along, check out the repo for this series here

Static: This folder holds stuff that doesn’t change. The details are laid out here. If you don’t have a tool chain that spits out new content with each build then you’ll probably have any CSS files and JavaScript files that you’ve written placed here. For us, we’ll be putting our artifacts from SASS here.

Themes: when you use Hugo themes, this directory will store the files responsible for making the magic happen. You could also make your own theme and put it here, but if you’re rolling your own there’s not much reason to do that. If you’re following along with the rest of getting Material set up, then this folder doesn’t matter much.

That was brief, right?

Let’s Finally Get to Style

You know everything you need to know about the file system, so this next part should be pretty easy.

If you’re lazy (and you should be), then check out the canvas repo, and if you’re following along, you can check out the save we had for our last article here. Use the 01/ directory to start, I’ll assume you’re working with that template. Make sure to make some content, otherwise you may not be able to see where things are being added / removed. My go-to test post:

hugo new post/test-post.md

Let’s check out the layouts/partials/head.html, and make an update to it.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>New Hugo Website</title>
<!-- add this line -->
<link rel="stylesheet" href="/css/canvas.css" type="text/css">
</head>
<body>

The link tells your website to get the file at the specified location and use it as a style sheet. Finally! We can start messing with this.

Looking at the /css/canvas.css file, what you’ll see some pretty simple stuff. How do we get Material in there? Well RTD, and let’s get started

The CDN Way

CDN’s are basically the internet’s version of billboards that you use to build other billboards. Maybe there’s not a good analogy, but essentially you’re telling your website to pick up a resource from someone else and use that. The benefit is that people who go to your website won’t need to re-download something they’ve already seen. For example, if you are using bootstrap and your user goes to twitter on whatever machine they’re using, if you’re using the CDN version of Bootstrap, then the browser will use the copy they got from twitter on your website, and skip downloading it again. How efficient!

Well, what if they don’t already have that? Well, now your site has to wait for them to download the entire thing. If you’re using the entire library or close to it, then there’s no reason not to use a CDN, so here’s how to do it.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>New Hugo Website</title>
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<link rel="stylesheet" href="/css/canvas.css" type="text/css">
</head>
<body>

More info on the Material.io page, which you should probably just read anyway! That’s pretty nice, but if you’re not using all of the library, and you don’t want to rely on the cache (which is a decision you have to make), then let’s check out that “Installing Locally” bit.

The Local Install

If you’re like me and you’re still reading this ever lengthening article, you’re going to want to control which components your site is using, with the benefit that you can reduce how much data you have to send. So install that library over npm.

npm i material-components-web

This makes a new directory “node_modules”, and downloads a bunch of stuff to make the Material.io components work. You don’t strictly need webpack to get this going, so I’m going to skip it: let’s get it going!

Setting Up the SCSS Pipeline

Let’s get organized! I like to put all of the stuff that isn’t shipping by itself in an “src” folder. Mine has a subdirectory for JavaScript, SASS, and ‘assets’ which is what I call images and icons. Inside of src/sass make an entry point SASS file src/sass/main.scss which is where all of your SASS files will funnel through. To start, if you like the style that comes with that template repo, copy-paste that right into the main.scss file, and delete the old static/css/canvas.css file.

Now you can’t serve SASS files, well you can but they won’t be interpreted by most browsers. They need to be transformed or compiled into CSS. To compile them into CSS run this little bit in a shell

sass .\src\sass\main.scss .\static\css\canvas.css

If you run that, you’ll be back at square 1, but now we have a pipeline set up. Let’s tidy up our workspace and get a little more complicated.

In ./src/sass/ create the following folders: “abstracts”, “base”, & “vendors”. Abstracts is where we keep our site-wide variables: things like background foreground and main color palette. Base is where the most fundamental styles will go, and they should be overwritten by things in something like a “components” directory. Finally, the vendors folder is where we want to keep our external SCSS imports.

You’ll notice in our current environment, we have colors, sizes and everything bundled together in one CSS file. This is fine if your site stays simple, but I already know mine won’t, so let’s break things out.

First, color: in make 2 new SCSS files in src/sass/abstracts/, \_colors.scss, and \_index.scss. \_index.scss is super simple:

@forward 'colors';

As your style sheets grow and you get more intricate code, it’s helpful to have a single point to grab all the things and dump them into your folder. In \_colors.scss we’re going to extract all of the colors for later;

$primary: #07a;
$accent: #941352;
$white: #fefefe;
$black: #454545;
$background: $white;

Finally, let’s actually use this in our main.scss file

@use 'abstracts/index' as *;

html {
    background-color: $background
}

body {
    color: $black;
    font-size: 18px;
    margin: 2em auto;
    max-width: 800px;
    padding: 1em;
    line-height: 1.6;
}

h1, h2, h3 {
    line-height:1.2
}

a {
    color: $primary
}

a:visited {
    color: $accent
}

Breaking it down, the @use directive picks up the index file and brings in all of the variables we just moved there and adds them to the local namespace (that’s what the * means). You can get more fancy and use the “as” keyword, and if you do you will also need to prepend that new namespace to all the variables within. This is useful if you are using vendor packages and those imports conflict with your names.

Make Some Changes

If you’re running the site it’s probably looking the same, and it’s probably pretty stark and blinding. I like dark mode for most stuff, so let’s go ahead and swap background and foreground.

// in abstracts/_colors.scss
$background: $black;


// in _main.scss
html {
    background-color: $background
}

body {
    color: $white;
    font-size: 18px;
    margin: 2em auto;
    max-width: 800px;
    padding: 1em;
    line-height: 1.6;
}

That should be a bit more kind on the eyes. Now I know it seems like I made a baffling decision to wrap the background color in another variable, but this technique has served me well to keep a consistent palette. When your style sheet gets more complicated, the number of things that need to consistently reference one element tends to get wide, meaning you’ll find yourself updating 1 value over 51 places.

Break it Down

Let’s break it down further for organization’s sake. Let’s rip out all of the block sizing and page structure into our src/sass/base folder. Again, make that \_index.scss file and a new file src/sass/base/\_base.scss. Move all the body and html tag to the new file and make sure to include the @use directive. It’ll look something like this:

@use 'abstracts/index' as *;

html {
    background-color: $background
}

body {
    color: $white;
    font-size: 18px;
    margin: 2em auto;
    max-width: 800px;
    padding: 1em;
    line-height: 1.6;
}

Let’s also break out text style. Make a new file, src/sass/base/\_text.scss and add it to the src/sass/base/\_index.scss file. Grab the header and anchor styles and drop them in. Again make sure to grab the @use directive.

@use 'abstracts/index' as *;

h1, h2, h3 {
    line-height:1.2
}

a {
    color: $primary
}

a:visited {
    color: $accent
}

To get those styles back in to your compiled CSS, make sure you @use the index file:

@use 'base/index' as base;

Now that we’ve done this, your SCSS compiler is probably complaining about not seeing the module, and this is because SASS doesn’t know where the root of the stlyes is. This is a simple fix: run this in your shell of choice instead of the simpler version from before.

sass .\src\sass\main.scss .\static\css\canvas.css --load-path=.\src\sass\

Now you’re telling SASS to consider that .\src\sass\ path as a root for resolving those names. After all of this you should have the same styles, but more organization. Let’s add some Material.io styling with something simple, like a button.

Button: Click Here to do the Thing

Let’s look at the index.html file, and pick a good place to put a button.

<section>
<!-- this section is populated by pulling posts from the site -->
{{ range first 1 (where .Pages.ByPublishDate.Reverse "Section" "posts") }}
    <article>
        <header>
          <h2>{{ .Title }}</h2>
          <p><time datetime="{{.Date}}">{{ .Date.Format "Mon, Jan 2, 2006" }}</time></p>
        </header>
        {{ .Summary }}
        <nav>
          <ul>
            <li><a href="{{ .RelPermalink }}">Read More &raquo;</a></li>
          </ul>
        </nav>
    </article>
{{ end }}
<footer>
  <a href="{{ "posts/" | absURL }}">All posts...</a>
</footer>
</section>

Breaking this down: the first Shortcode grabs the most recent content type that’s a “post”. So whatever markdown document that’s in the /content/posts that’s got the most recent PublishDate will get pulled into this HTML. I’ve already covered omst of how this page works in the previous article. What I’m interested in is that <nav> section. That’s a pretty good place for a button: you click it and you go to see the rest of the article. What we’re going to do is wrap that bit up in the html necessary for the Material.io component library to swoop in and beautify it.

RTD

If you’re using a CDN skip to the next section since you’re already dabbling in forbidden magics.

Add the Material Vendor

One of the first things the documentation says is how to get the styles! I mentioned earlier that we want to keep those in our vendors folder, so let’s set that up.

You may expect us to make another index file, but for vendors import order is usually important, so I’ll sometimes elect to make individual vendor import files. In this case make a new file, “src/sass/vendors/_material.scss” and copy-paste the code from the docs into that file. In the “src/sass/main.scss” file make sure to add a @use directive:

@use "vendors/material" as material;

And with this, you’ll also need to include the “node_modules” directory.

sass --watch .\src\sass\main.scss .\static\css\canvas.css --load-path=.\src\sass\ --load-path=.\node_modules

Now unless you’ve got some SCSS errors you should be good to go.

Cooking with Material

We should be good to go with Material buttons (and other things if you’re CDN-ing), so let’s add that component to the “Read More” link.

The simplest possible button you can do is the text button.

<!-- old -->
{{ range first 1 (where .Pages.ByPublishDate.Reverse "Section" "posts") }}
<!-- skip to the real stuff -->
<nav>
  <ul>
    <li><a href="{{ .RelPermalink }}">Read More &raquo;</a></li>
  </ul>
</nav>
{{ end }}
<!-- end tag to make sure I don't feel like we're left hanging -->


<!-- new -->
{{ range first 1 (where .Pages.ByPublishDate.Reverse "Section" "posts") }}
<a href="{{ .RelPermalink }}">
  <button class="mdc-button">
    <div class="mdc-button__ripple"></div>
    <span class="mdc-button__label">Read More</span>
  </button>
</a>
{{ end }}

Those classes signal material to update the styling, and those double underscore classes do fancier things when you import JavaScript, but we’ll leave that for another probably equally long article. Remember to close that range shortcode with an {{ end }} otherwise Hugo will complain and you’ll get an error for your website.

What more Awaits

So now you have a button that works with Material and hosting on Hugo! And yeesh, it still needs some love. You have all the tools to do that, and in the next article we’ll make some more tweaks to the styles and I’ll go into how to set thing up to be a bit more aesthetically pleasing. Double check against my repo, under /02 and you will probably end up mostly where I am.

If you followed through you should have a site that’s got Material components, a pipeline to get SCSS transformed to CSS, and getting that working with a Hugo site.