26.2 SCSS Variables, Nesting, Mixins, and Imports
Alright, let’s get our hands dirty. You’re using Hugo, which means you’re already a step ahead of the game because Hugo’s built-in Sass/SCSS compilation is fantastic. It means we can write clean, powerful, and, most importantly, DRY (Don’t Repeat Yourself) stylesheets and let Hugo handle the messy conversion to plain CSS for the browser. This is where SCSS becomes your best friend, and Tailwind is the quirky, super-efficient roommate who keeps things interesting.
Why Bother with SCSS if You’re Using Tailwind?
I know what you’re thinking. “Tailwind is utility-first! I just write classes in my HTML!” And you’re right. But Tailwind is a framework, and your project is a unique snowflake. SCSS is how you bridge that gap. You’ll use it to:
- Configure Tailwind: Your
tailwind.config.jsis great, but the real magic starts with SCSS variables for your theme. - Create Custom Components: For those times when a
btn-primaryclass is infinitely more readable thanbg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded. - Manage Logic: Loops, conditionals, and functions for generating styles? Yes, please. Tailwind doesn’t do that on its own.
Think of it this way: Tailwind gives you a giant box of Lego bricks. SCSS is the tool you use to pre-assemble larger, reusable chunks so you don’t have to stick the same window piece on the same wall piece 50 times.
Taming the Beast: SCSS Variables and Tailwind’s Config
The first place SCSS and Tailwind meet is in your theme. Don’t you dare hard-code hex values in both places. That’s a maintenance nightmare waiting to happen. Instead, we define our palette in SCSS first, then feed those values into Tailwind.
// assets/scss/_variables.scss
// Our single source of truth for brand colors
$color-primary: #3498db;
$color-secondary: #2ecc71;
$color-accent: #e74c3c;
$color-dark: #2c3e50;
$color-light: #ecf0f1;
Now, let’s pipe these into Tailwind. We do this by creating a custom SCSS file that Tailwind will process. Hugo’s asset pipeline is perfect for this.
// assets/scss/tailwind.scss
@tailwind base;
@tailwind components;
@tailwind utilities;
// Import our variables AFTER Tailwind's defaults
@import 'variables';
// Now we extend the Tailwind config with our colors
@layer base {
:root {
--color-primary: #{$color-primary};
--color-secondary: #{$color-secondary};
--color-accent: #{$color-accent};
--color-dark: #{$color-dark};
--color-light: #{$color-light};
}
}
Then, in your tailwind.config.js, you can reference these CSS custom properties, keeping everything consistent.
// tailwind.config.js
const colors = require('tailwindcss/colors')
module.exports = {
content: ["./layouts/**/*.html", "./content/**/*.md"],
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
accent: 'var(--color-accent)',
// You can still use Tailwind's built-ins alongside your custom ones
gray: colors.neutral,
}
}
}
}
The Art of the Component: @layer and @apply
This is where people often go wrong. They see @apply and think it’s a free pass to recreate Bootstrap. Resist that urge. @apply is for creating small, reusable utilities and components that are still composed of Tailwind’s atomic classes. Don’t build monolithic, multi-purpose classes with it; you’ll just lose the benefits of Tailwind.
The @layer directive is crucial here. It tells Tailwind which “bucket” (base, components, utilities) to put our generated CSS into. This is vital for controlling specificity and the order of your styles.
// assets/scss/components/_buttons.scss
@layer components {
.btn {
@apply font-bold py-2 px-4 rounded no-underline inline-block transition-colors duration-200;
// Look, we can even use our SCSS variables for things Tailwind doesn't cover!
box-shadow: 0 2px 4px rgba($color-dark, 0.2);
&-primary {
@apply bg-primary text-white;
&:hover {
@apply bg-[#2980b9]; // Using an arbitrary value for a one-off hover state
}
}
&-secondary {
@apply bg-secondary text-dark;
&:hover {
@apply bg-[#27ae60];
}
}
}
}
See what we did there? We used nesting (&-primary) to keep our code organized and logical. The main .btn class gets the base styles, and the modifiers build on top of it. This is SCSS and Tailwind working in perfect harmony.
Mixins: Your Secret Weapon for Repetitive Complexity
Let’s say you have a fancy card that appears in multiple places with slight variations. Writing the same @apply rules repeatedly is error-prone. This is a job for a mixin.
// assets/scss/mixins/_card.scss
@mixin card-variant($bg-color: white, $border-color: gray) {
@apply p-6 rounded-lg border;
background-color: $bg-color;
border-color: $border-color;
.card-title {
@apply text-xl font-semibold mb-2 text-dark;
}
}
// Then, in your components...
@layer components {
.card-default {
@include card-variant; // Uses defaults
}
.card-highlight {
@include card-variant($color-light, $color-primary);
}
}
Now you have a single source of truth for your card styles. Want to change the padding on all cards? You change it in one place, the mixin. This is the power of SCSS that pure Tailwind can’t match.
The Hugo-specific Gotcha: Imports and Partials
Hugo expects your main SCSS file to be in assets/scss/. Let’s call it main.scss. This file should be a manifest that imports everything else. The underscore in _variables.scss is a Sass convention meaning “this is a partial”; it tells the compiler not to try to compile it into a CSS file on its own. Hugo respects this.
// assets/scss/main.scss
// First, we pull in our Tailwind stuff
@import 'tailwind';
// Then our project-specific layers
@import 'variables';
@import 'mixins/card';
@import 'components/buttons';
@import 'components/header';
// Your own custom utilities that Tailwind doesn't provide
@layer utilities {
.text-shadow {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
}
The order here is critical. You want Tailwind’s base styles first, then your variables, then your components. This ensures the Cascade does its job correctly and your styles override Tailwind’s defaults as intended. If you get this wrong, you’ll spend an hour yelling at your browser’s dev tools wondering why your style isn’t applying. I’ve been there. Learn from my pain.
Finally, reference this in your template’s <head>:
{{ $styles := resources.Get "scss/main.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}">
Hugo will take it from here, running the entire Sass compilation pipeline and giving you a perfectly optimized CSS file. It’s genuinely one of Hugo’s killer features. Use it.