Exercises
Six drills to lock in modern SCSS habits — modules, maps, mixins with @content, responsive tokens, and dark-mode-friendly components. Try each in your own scratch file before peeking at the solution.
Six SCSS drills with answers
EXAMPLE
// ============================================================
// Drill 1 — Replace a legacy @import with @use
// ============================================================
// GIVEN:
// @import 'tokens'; // exposes $primary, $radius
// .btn { color: $primary; border-radius: $radius; }
//
// TASK: convert to @use without losing variable access.
//
// SOLUTION:
@use 'tokens';
.btn { color: tokens.$primary; border-radius: tokens.$radius; }
// Or alias:
// @use 'tokens' as t; .btn { color: t.$primary; }
// ============================================================
// Drill 2 — A token map you can read deeply
// ============================================================
// TASK: build $tokens with nested colour ramps and a 'token()' function
// SOLUTION:
@use 'sass:map';
$tokens: (
'color': (
'brand': (500: #2563eb, 600: #1e40af),
'gray': (50: #f8fafc, 900: #0f172a),
),
'space': ('sm': .5rem, 'md': 1rem, 'lg': 2rem),
);
@function token($path...) {
$v: $tokens;
@each $k in $path { $v: map.get($v, $k); }
@if $v == null { @error 'Unknown token: #{$path}'; }
@return $v;
}
.brand { color: token('color','brand', 500); padding: token('space','md'); }
// ============================================================
// Drill 3 — A mixin that takes a block (@content)
// ============================================================
// TASK: write 'hover-only' that emits the body only on devices that hover.
// SOLUTION:
@mixin hover-only { @media (hover: hover) and (pointer: fine) { &:hover { @content; } } }
.link { @include hover-only { text-decoration: underline; } }
// ============================================================
// Drill 4 — Mobile-first breakpoint mixin from a map
// ============================================================
$bp: ('sm': 480px, 'md': 768px, 'lg': 1024px, 'xl': 1280px);
@mixin respond-to($name) {
@if not map.has-key($bp, $name) { @error 'Unknown bp #{$name}'; }
@media (min-width: map.get($bp, $name)) { @content; }
}
.card { padding: 1rem; @include respond-to('md') { padding: 1.5rem; } }
// ============================================================
// Drill 5 — Themable component via CSS variables
// ============================================================
// TASK: build .btn whose colour can flip per .theme-dark without recompiling.
// SOLUTION:
:root { --btn-bg: #{token('color','brand',500)}; --btn-fg: white; }
.theme-dark { --btn-bg: #{token('color','gray',50)}; --btn-fg: #{token('color','gray',900)}; }
.btn {
background: var(--btn-bg); color: var(--btn-fg);
padding: token('space','sm') token('space','md');
border-radius: 6px; border: 0;
}
// ============================================================
// Drill 6 — Generate utility classes from a map
// ============================================================
@each $name, $value in map.get($tokens, 'space') {
.p-#{$name} { padding: $value; }
.px-#{$name} { padding-inline: $value; }
.my-#{$name} { margin-block: $value; }
}
// ============================================================
// Scoring
// ============================================================
// 6 / 6 -> can run a codebase migration off @import this afternoon
// 4 / 6 -> bookmark the sass:map docs
// < 4 -> revisit the sass/cheatsheet lesson
Why it matters
Convert your stylesheets off @import as soon as your team can land a single PR doing it. Run `npx sass-migrator module .` and review the diff — most teams cut their stylesheet bundle, fix shadowing bugs, and unlock per-module namespacing in one sitting.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…