Website Markup with SASS/SCSS
SCSS is a syntactic superset of CSS with support for variables, nesting, mixins, functions, conditions, and loops. Compiles to clean CSS with no runtime dependencies. Dart Sass is the current reference implementation; LibSass is deprecated. In 2024, SCSS remains the standard for projects without React or with a requirement for minimal bundle without CSS-in-JS.
Installation
npm install -D sass
Vite supports .scss out of the box after installing sass:
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
// Automatically inject globals into each file
additionalData: `@use "@/styles/abstracts" as *;`,
api: 'modern-compiler', // Dart Sass 2.x API
},
},
},
});
7-1 Architecture
src/styles/
abstracts/
_variables.scss ← variables (sizes, breakpoints)
_colors.scss ← color palette
_functions.scss ← helper functions
_mixins.scss ← reusable blocks
_index.scss ← @forward everything from abstracts
base/
_reset.scss
_typography.scss
_root.scss ← CSS Custom Properties
components/
_button.scss
_card.scss
_nav.scss
_modal.scss
layout/
_grid.scss
_header.scss
_footer.scss
_section.scss
pages/
_home.scss
_about.scss
_contact.scss
themes/
_light.scss
_dark.scss
main.scss ← entry point, @use everything
// abstracts/_variables.scss
$grid-breakpoints: (
'xs': 0,
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1280px,
'2xl': 1536px,
) !default;
$container-max-widths: (
'sm': 540px,
'md': 720px,
'lg': 960px,
'xl': 1200px,
'2xl': 1400px,
) !default;
$spacers: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 0.75rem,
4: 1rem,
5: 1.5rem,
6: 2rem,
7: 3rem,
8: 4rem,
9: 6rem,
) !default;
$font-sizes: (
'xs': 0.75rem,
'sm': 0.875rem,
'base': 1rem,
'lg': 1.125rem,
'xl': 1.25rem,
'2xl': 1.5rem,
'3xl': 1.875rem,
'4xl': 2.25rem,
'5xl': 3rem,
) !default;
Mixins
// abstracts/_mixins.scss
// Media queries
@mixin respond-to($breakpoint) {
$value: map.get($grid-breakpoints, $breakpoint);
@if $value == null {
@error 'Unknown breakpoint: #{$breakpoint}';
}
@media (min-width: $value) {
@content;
}
}
// Flex centering
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: center;
}
// Text truncation
@mixin truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
// Visually hidden (accessibility)
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
// Fluid typography
@mixin fluid-type($min-size, $max-size, $min-width: 320px, $max-width: 1280px) {
font-size: $min-size;
@media (min-width: $min-width) {
font-size: clamp(
#{$min-size},
#{$min-size} + #{($max-size - $min-size)} * ((100vw - #{$min-width}) / #{($max-width - $min-width)}),
#{$max-size}
);
}
}
// Standardized focus ring
@mixin focus-ring($color: var(--color-accent)) {
&:focus-visible {
outline: 2px solid $color;
outline-offset: 2px;
}
}
Components
// components/_button.scss
@use '../abstracts' as *;
.btn {
@include flex-center;
@include focus-ring;
gap: 0.5rem;
font-family: inherit;
font-weight: 500;
border: 1px solid transparent;
cursor: pointer;
white-space: nowrap;
text-decoration: none;
transition:
background-color 150ms ease,
border-color 150ms ease,
color 150ms ease,
transform 100ms ease;
&:active { transform: scale(0.98); }
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
// Sizes
&--sm {
height: 32px;
padding: 0 12px;
font-size: map.get($font-sizes, 'sm');
border-radius: 4px;
}
&--md {
height: 40px;
padding: 0 16px;
font-size: map.get($font-sizes, 'base');
border-radius: 8px;
}
&--lg {
height: 48px;
padding: 0 24px;
font-size: map.get($font-sizes, 'lg');
border-radius: 8px;
}
// Variants
&--primary {
background: var(--color-accent);
color: #fff;
&:hover:not(:disabled) { background: var(--color-accent-dark); }
}
&--ghost {
background: transparent;
color: var(--color-accent);
border-color: var(--color-accent);
&:hover:not(:disabled) { background: var(--color-accent-light); }
}
&--full { width: 100%; }
}
// layout/_grid.scss
@use '../abstracts' as *;
.container {
width: 100%;
margin-inline: auto;
padding-inline: 1rem;
@each $name, $max-width in $container-max-widths {
@include respond-to($name) {
max-width: $max-width;
}
}
}
.row {
display: flex;
flex-wrap: wrap;
margin-inline: -0.75rem;
}
@for $i from 1 through 12 {
.col-#{$i} { flex: 0 0 auto; width: percentage(math.div($i, 12)); }
}
@each $bp, $value in $grid-breakpoints {
@if $value > 0 {
@include respond-to($bp) {
@for $i from 1 through 12 {
.col-#{$bp}-#{$i} { flex: 0 0 auto; width: percentage(math.div($i, 12)); }
}
}
}
}
Main File
// main.scss
@use 'sass:math';
@use 'sass:map';
@use 'sass:color';
@use 'base/reset';
@use 'base/root';
@use 'base/typography';
@use 'layout/grid';
@use 'layout/header';
@use 'layout/footer';
@use 'components/button';
@use 'components/card';
@use 'components/nav';
@use 'pages/home';
@use 'pages/about';
Timeline
Setting up architecture and basic abstractions: 3–5 hours. Website markup: comparable to regular CSS with a ready mixin system. Corporate website of 5–8 pages: 4–7 days.







