A design system isn’t just a collection of buttons and colors — it’s the backbone of your product’s visual and structural logic. And if it’s done right, it feels like cheating: intuitive, scalable, and almost magical.
This guide is for front-end developers who want to implement a rock-solid system that’s just as beautiful to maintain as it is to look at. It’s about performance, clarity, and making sure no one ever writes margin: 42px again.
SCSS vs CSS Variables: Who Does What?
CSS variables (--primary-color) are dynamic. They can be changed at runtime, by JS, or in a media query. So, they’re perfect for themes, responsiveness, or user preferences. SCSS variables ($primary) are static at compile time and great for logic, maps, and dev comfort.
Our advice: Use CSS variables for anything that may vary (colors, spacing, themes). Use SCSS maps + functions for static logic.
Want to go full CSS variable for readability? Great — just be consistent. The best system is the one you’ll love to maintain.
Typography: Let It Scale
// _typography.scss
// Variables
--font-family-titles: 'Your font custom', 'Arial', sans-serif;
$font-size-base: 1rem;
$font-sizes: (
xxxs: 0.75rem,
xxs: 0.8125rem,
xs: 0.875rem,
s: $font-size-base,
m: 1.125rem,
l: 1.25rem,
xl: 1.5rem,
xxl: 2rem,
body-s: 0.75rem;
body-m: $font-size-base;
body-l: 1.125rem;
);
$line-height-titles: 1.2;
$line-height-global: 1.5;
$font-weights: (
light: 300,
regular: 400,
medium: 500,
bold: 700
);
// Headings & Body base
%heading {
font-family: var(--font-family-titles);
font-weight: $boldish;
line-height: $line-height-titles;
text-transform: uppercase;
letter-spacing: 2px;
}
%body-base {
font-family: var(--font-family-base);
font-weight: $regular;
line-height: $line-height-global;
}
@mixin font-size($key, $weight: regular) {
font-size: map-get($font-sizes, $key);
font-weight: map-get($font-weights, $weight);
}
// Headings
%heading-xxl { @include font-size(xxl, bold); }
%heading-xl { @include font-size(xl, bold); }
%heading-l { @include font-size(l, medium); }
%heading-m { @include font-size(m, medium); }
%heading-s { @include font-size(s); }
%heading-xs { @include font-size(xs); }
// Body
%body-l { @include font-size(body-l); }
%body-m { @include font-size(bpdy-m); }
%body-s { @include font-size(body-s); }
//Headings & Body Utility classes
h1 { @extend %heading-xxl; }
h2 { @extend %heading-xl; }
h3 { @extend %heading-l; }
h4 { @extend %heading-m; }
h5 { @extend %heading-s; }
h6 { @extend %heading-xs; }
body { @extend %body-m; }
Buttons With Options
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
border: none;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
@include font-size(m);
}
.button-primary {
@extend .button;
background-color: var(--color-primary);
color: #fff;
}
.button-secondary {
@extend .button;
background-color: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary);
}
.button svg {
width: 1rem;
height: 1rem;
}
Layout Strategy: Structure Smart
The layout should be controlled at the .section level. Each section gets a padding-inline and margin-bottom, using our spacer system.
$spacers: (
none: 0,
xs: 0.5rem,
s: 1rem,
m: 2rem,
l: 4rem,
xl: 6rem
);
@mixin section-spacing($padding: m, $margin: l) {
padding-inline: map-get($spacers, $padding);
margin-bottom: map-get($spacers, $margin);
}
.section {
@include section-spacing();
}
div.x {
padding-inline: map-get($spacers, s);
}
Final SCSS Starter Kit (2025 Edition)
// scss/core/_variables.scss
// Dynamic variables (CSS)
:root {
--font-family-titles: 'Your titles custom font', 'Arial', sans-serif;
--font-family-base: 'Your body custom font', 'Arial', sans-serif;
}
// Static variables (SCSS)
$font-sizes: (
xxxs: 0.75rem,
xxs: 0.8125rem,
xs: 0.875rem,
s: 1rem,
m: 1.125rem,
l: 1.25rem,
xl: 1.5rem,
xxl: 2rem,
body-s: 0.75rem,
body-m: 1rem,
body-l: 1.125rem
);
$line-heights: (
titles: 1.2,
global: 1.5
);
$font-weights: (
light: 300,
regular: 400,
medium: 500,
bold: 700
);
$spacers: (
none: 0,
xs: 0.5rem,
s: 1rem,
m: 2rem,
l: 4rem,
xl: 6rem
);
$colors: (
primary: #007bff,
secondary: #6c757d,
// Add more colors as needed
);
// scss/core/_functions.scss
@function spacing($key) {
@return map-get($spacers, $key);
}
// scss/core/_mixins.scss
@mixin font-size($key, $weight: regular) {
font-size: map-get($font-sizes, $key);
font-weight: map-get($font-weights, $weight);
}
@mixin section-spacing($padding: m, $margin: l) {
padding-inline: spacing($padding);
margin-bottom: spacing($margin);
}
// scss/core/_typography.scss
%heading {
font-family: var(--font-family-titles);
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
}
%body-base {
font-family: var(--font-family-base);
font-weight: $regular;
line-height: $line-height-global;
}
// Headings & Body Utility classes
h1 { @extend %heading; @include font-size(xxl); }
h2 { @extend %heading; @include font-size(xl); }
h3 { @extend %heading; @include font-size(l, medium); }
h4 { @extend %heading; @include font-size(m, medium); }
h5 { @extend %heading; @include font-size(s); }
h6 { @extend %heading; @include font-size(xs); }
body { @extend %body-base; @include font-size(body-m); }
// scss/components/_buttons.scss
@use 'core' as *;
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
border: none;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
@include font-size(m);
}
.button-primary {
@extend .button;
background-color: map-get($colors, primary);
color: #fff;
}
.button-secondary {
@extend .button;
background-color: transparent;
color: map-get($colors, primary);
border: 1px solid map-get($colors, primary);
}
.button svg {
width: 1rem;
height: 1rem;
}
// scss/components/_layout.scss
.section {
@include section-spacing();
}
// scss/main.scss
@use 'core/index' as *;
@use 'components/buttons';
@use 'components/layout';
This structure ensures readability, performance, and long-term maintainability. It feels light, but it runs deep.
TL;DR? A great design system doesn’t shout. It whispers, ‘don’t worry, I’ve got this.’ Use placeholders, employ mixins like your favorite secret sauce, comment everything like you’re writing a love letter to future-you, and build something that even a grumpy developer on a Monday morning will appreciate. Remember, there’s no point in crafting overly complex systems just for show. Sure, it might impress at first, but if it’s not maintainable, devs get lost, onboarding becomes a labyrinth, and soon your design system is like that one cousin who brings too many complicated board games to family gatherings—well-intentioned but utterly bewildering.