5  CSS/SCSS

5.1 General CSS

There are also changes where you just want to modify the CSS directly, these changes should be applied in the scss:rules section. For example, we might want to add a good underline to links in the slides themselves.

Generally this would be done like so:

a {
  text-decoration: underline;
}

but it won’t work because there are other attributes with higher specificity. Note that CSS rules that target Reveal content generally need to use the .reveal .slide prefix to successfully override the theme’s default styles.

.reveal .slide a {
  text-decoration: underline;
}

And the changes have been applied.

5.2 Using CSS Classes

CSS classes are a quick and powerful way to add styling to your slides.

remember how we set up theme colors in applying colors? We should have a way to apply these colors in our slides. For starters let us add a way to turn text these colors. Below are two CSS classes, named .blue and .yellow, that change the color and make the text bold with font-weight: bold;. Note that we need to put these classes into the scss:rules section.

/*-- scss:rules --*/

$theme-blue: #219ea7ff;
$theme-yellow: #F4BA02;

.blue {
  color: $theme-blue;
  font-weight: bold;
}

.yellow {
  color: $theme-yellow;
  font-weight: bold;
}

Now that we have our classes defined, we can apply these changes in the source editor by using the [text]{.class} syntax. I added (slightly excessive) highlighting to a couple of words. See below

[Quarto]{.blue} enables you to weave together [content]{.yellow} and [executable code]{.yellow} into a **finished presentation**. To learn more about [Quarto]{.blue} presentations see <https://quarto.org/docs/presentations/>.

The idea behind CSS classes are quite powerful. We are essentially adding CSS attributes to specific elements of our slides, without needing to figure out how to target it with CSS selectors.

You can also apply CSS classes to whole sections of the slide using fenced divs.

::: blue
Some paragraph.
:::

or

::: {.blue}
Some paragraph.
:::

5.3 Using Maps to store theme colors

Before Maps I would have specified a color palette theme as

$theme-red: #FA5F5C;
$theme-blue: #394D85;
$theme-darkblue: #13234B;
$theme-yellow: #FFF7C7;
$theme-white: #FEFEFE;

But using a map it turns into the following

$colors: (
  "red": #FA5F5C,
  "blue": #394D85,
  "darkblue": #13234B,
  "yellow": #FFF7C7,
  "white": #FEFEFE
);
Note

There is a difference between (key: value) and ("key": value) in SASS. For consistency, I also use quoted strings as the keys in a map.

Instead of having multiple values representing our colors we now just have one $colors. By themselves, maps aren’t valid CSS and don’t do anything once the SASS compiles. The next sections will show how we can use these maps more efficiently than my previous approach.

5.4 Using Functions to pull out theme colors

We where using these colors to change a lot of things, including the major Sass Variables revealjs sass variables. Before we used a map, it would look something like this:

$body-bg: $theme-yellow;
$link-color: $theme-blue;
$code-color: $theme-blue;
$body-color: $theme-darkblue;

but we can’t do that directly with a map. There are built-in functions to extract values from a map, namely the function map-get(). Using that we can rewrite the above as

$body-bg: map-get($colors, "yellow");
$link-color: map-get($colors, "blue");
$code-color: map-get($colors, "blue");
$body-color: map-get($colors, "darkblue");

And while that is all good, I find it a little nicer to have a helper function to do this.

Functions in SASS are written as below. You can use as many arguments as you want.

@function name($arg1, $arg2) {
  @return $arg1 + $arg2;
}

The helper function I wrote is a light wrapper around map-get() to avoid having to write $colors.

@function theme-color($color) {
  @return map-get($colors, $color);
}

And we now have the final rewrite.

$body-bg: theme-color("yellow");
$link-color: theme-color("blue");
$code-color: theme-color("blue");
$body-color: theme-color("darkblue");
Note

Note that we are using theme-color("yellow") instead of theme-color(yellow) because we used quoted strings in the map. Using unquoted strings all around gave me false positives in my IDE as it interpreted yellow inside theme-color() as #FFFF00 instead of my theme value.

5.5 Using @each to automatically create CSS classes

To add a splash of color or as used in highlighting, I would create a lot of CSS classes like so:

.text-red {
  color: $theme-red;
}
.text-yellow {
  color: $theme-yellow;
}
.text-blue {
  color: $theme-blue;
}

.bg-red {
  background-color: $theme-red;
}
.bg-yellow {
  background-color: $theme-yellow;
}
.bg-blue {
  background-color: $theme-blue;
}

Which is all fine and dandy until you also want a class for underlining. It becomes a lot of copy-pasting and changing a couple of names. And that is not to mention the trouble you run into when you decide to add a new color into the mix halfway through your slides.

This is where SASS interpolation comes into place and the moment I realized maps were worth it. Interpolation is done by using #{} in some code, meaning that if $favorite-color: "blue" then .text-#{$favorite-color} {} turns into .text-blue {}. SASS provides the action @each to loop over all the key and value pairs of our map. So we can rewrite the creation of the above classes as this:

@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }

  .bg-#{$name} {
    background-color: $color;
  }
}

And this is the beauty of maps. If I want to add a new color to my slides, I just have to add it to the $colors map. If I want to add a new set of classes, I just have to write it once inside the @each statement.

5.6 Using @mixin to avoid repeating code

There are times where you end up needing to write almost the same CSS over and over again. If this happens to use it is a sign that you should try using mixins.

Take the following CSS code.

.theme-slide1 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide1.svg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
}
.theme-slide2 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide2.svg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
}
.theme-slide3 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide3.svg');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
}

In each of of the chunks we are having the same values of background-size, background-position, and background-repeat. We can use a @mixin as a sort of macro. When the SCSS compiles each instance of @include background-full; will be replaced by what is listed inside @mixin background-full {}. Giving us the following code.

@mixin background-full {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.theme-slide1 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide1.svg');
    @include background-full;
  }
}
.theme-slide2 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide2.svg');
    @include background-full;
  }
}
.theme-slide3 {
  &:is(.slide-background) {
    background-image: url('../../../../../assets/slide3.svg');
    @include background-full;
  }
}

But there is still some amount of repetition, with that we use the fact that @mixin can have arguments. Combineing this lets us create 1 @mixin and 3 calls to @include. One for each instance of the CSS class.

@mixin theme-slide($number) {
  .theme-slide#{$number} {
    &:is(.slide-background) {
      background-image: url('../../../../../assets/slide#{$number}.svg');
      @include background-full;
    }
  }
}

@include theme-slide(1);
@include theme-slide(2);
@include theme-slide(3);

It becomes quite a bit tighter! But we can do better because SASS also has @for loops.

@mixin theme-slide($number) {
  .theme-slide#{$number} {
    &:is(.slide-background) {
      background-image: url('../../../../../assets/slide#{$number}.svg');
      @include background-full;
    }
  }
}

@for $i from 1 through 3 {
  @include theme-slide($i);
}

you can now add more background images with ease as long as you are careful when naming them.

5.7 Using nested loops to create classes

In our first example, I want a fun gradient shadow/highlight effect for text. We can get that effect using something like following

background-image: linear-gradient(90deg, yellow, blue);
background-size: 100% 42%;
background-repeat: no-repeat;
background-position: 0 85%;
width: fit-content;

I want it to work on both inline text and as a way to handle the header, the selectors for that would be span.my-class and .my-class > h2 respectively.

span.my-class, .my-class > h2 {
  background-image: linear-gradient(90deg, yellow, blue);
  background-size: 100% 42%;
  background-repeat: no-repeat;
  background-position: 0 85%;
  width: fit-content;
}

And them we fill in the rest, using the @each command twice nestedly over a map of colors.

/*-- scss:defaults --*/

$colors: (
  "red": #FFADAD,
  "orange": #FFD6A5,
  "yellow": #FDFFB6,
  "blue": #aad2e7,
  "purple":#b4addd
);

/*-- scss:rules --*/

@each $name1, $col1 in $colors {
  @each $name2, $col2 in $colors {
    span.hl-#{$name1}-#{$name2}, .hl-#{$name1}-#{$name2} > h2 {
      background-image: linear-gradient(90deg, $col1, $col2);
      background-size: 100% 42%;
      background-repeat: no-repeat;
      background-position: 0 85%;
      width: fit-content;
    }
  }
}

I know we are creating some non-interesting classes such as .hl-yellow-yellow but for what we are doing, the tradeoff between avoiding them and how little it impacts us to have them. I think it is a worthwhile tradeoff.

Important

The slides in this post are interactive, advance them to see the other classes.

qmd scss

Note

You don’t need these compound classes for everything. For example, the class .hl-green-bold isn’t going to be useful as you could just as easily create .hl-green and .bold separately. This trick works best when two elements are used together in a tightly coupled way, such as in gradients.

For our second example, we are continuing with the gradients, but instead trying to apply them to the background. My goal was to add a gradient line to the right side of the slide.

I was able to create that effect, by layering 2 gradients on top of each other. The first gradient contained the two colors I was interested in, and the names of the class. The second layer, which I placed on top, goes from white to transparent. I set up the transition between those two colors to be super sharp, resulting in the effect you see below

qmd scss

since we are doing something interesting, we could also have used a separate $colors map just for this effect to not interfere with what else we are doing.