Sass — мощный препроцессор CSS

Sass (Syntactically Awesome Style Sheets) расширяет CSS переменными, вложенностью, миксинами и функциями. Браузеры его не понимают, поэтому файлы .scss компилируются в обычный .css — но писать и поддерживать стили становится заметно проще.

Уровень: средний 18 разделов ⏱ 2–3 часа

1. Что такое Sass и два синтаксиса

У Sass два синтаксиса:

Большинство проектов используют SCSS — он дружелюбен к новичкам, потому что похож на обычный CSS.

Все примеры курса написаны на SCSS. Если нужно быстро скомпилировать без настройки — есть онлайн-сервисы вроде sassmeister.com, но для проекта лучше настроить сборку.

2. Установка и компиляция

npm install --save-dev sass

# компиляция одного файла
npx sass styles.scss styles.css

# слежение за изменениями (watch)
npx sass --watch styles.scss styles.css

# минифицированный вывод
npx sass --style=compressed styles.scss styles.css

В реальных проектах Sass подключается в сборку: в Vite достаточно поставить sass и использовать <style lang="scss"> во Vue или импорты в React.

3. Переменные

$primary: #2563eb;
$spacing: 16px;
$font-base: "Segoe UI", Arial, sans-serif;

.button {
  background: $primary;
  margin: $spacing;
  font-family: $font-base;
}

Переменные компилируются в значения — в итоговом CSS их не будет. Для работы в рантайме браузера понадобятся CSS Custom Properties (--var), а Sass-переменные — для времени компиляции.

Не смешивай бездумно: Sass-переменные удобны для значений на этапе сборки (палитра, сетка), CSS-переменные — для динамических значений в браузере (темы, тёмный режим).

4. Вложенность

.card {
  padding: 16px;

  .card-title {
    font-size: 1.25rem;
  }

  &:hover {           // & — ссылка на родителя
    transform: scale(1.02);
  }

  &.active {          // .card.active
    border-color: $primary;
  }
}
// Компилируется в:
.card { padding: 16px; }
.card .card-title { font-size: 1.25rem; }
.card:hover { transform: scale(1.02); }
.card.active { border-color: #2563eb; }
Не увлекайся вложенностью глубже 3–4 уровней: каждая >-вложенность раздувает специфичность селекторов.

5. Частичные файлы: @use и @import

Файл, начинающийся с подчёркивания (например, _variables.scss), называется частичным и не компилируется в отдельный CSS. Его подключают в основной файл:

// _variables.scss
$accent: #f59e0b;

// _mixin.scss
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

// main.scss
@use "variables";
@use "mixins";

.hero {
  background: variables.$accent;
  @include mixins.flex-center;
}
Старый @import в Sass обесценен и заменён на @use: он подключает файл один раз, позволяет использовать неймспейсы и не греет глобальное пространство. В новых проектах пиши только @use.

6. Миксины: @mixin и @include

Миксины — переиспользуемые блоки свойств с параметрами:

@mixin button($bg, $color: #fff) {
  background: $bg;
  color: $color;
  border-radius: 8px;
  padding: 10px 16px;
}

.btn-primary { @include button(#2563eb); }
.btn-danger  { @include button(#dc2626); }
Чем отличается от @extend? Миксин копирует свойства в каждое место (логично с параметрами), а @extend группирует селекторы в одно правило и даёт более компактный CSS без параметров.

7. Операторы и расчёты

$base: 8px;

.title {
  font-size: $base * 3;       // 24px
  padding: $base * 2 $base;   // 16px 8px
}

.container {
  width: (100vw - 240px) / 2; // скобки считаются
}

Sass умеет складывать, вычитать, умножать, делить и брать остаток (%). Деление пишется в скобках или со / внутри операции, чтобы не конфликтовать со старым CSS (font: 16px/1.5).

8. Функции: @function

// Переводим пиксели в rem
@function rem($px, $base: 16px) {
  @return $px / $base * 1rem;
}

.title { font-size: rem(24); }   // 1.5rem

У Sass есть и встроенные функции: lighten(), darken(), mix(), rgba(), nth(), map-get() и другие.

$primary: #2563eb;
.button:hover {
  background: darken($primary, 8%);
}

9. Циклы, условия и @content

Sass — настоящий язык: в нём есть @for, @each, @if и блок @content:

@for $i from 1 through 4 {
  .col-#{$i} { width: 25% * $i; }
}

$breakpoints: (sm: 576px, md: 768px, lg: 992px);
@each $name, $px in $breakpoints {
  @media (min-width: $px) {
    .container-#{$name} { max-width: $px; }
  }
}

@mixin hover-bg($color) {
  &:hover {
    background: $color;
  }
}
.btn { @include hover-bg(#e5e7eb) { cursor: pointer; } }
Интерполяция #{$var} нужна там, где переменная участвует в имени селектора или свойстве. Внутри значений пиши переменную как обычно.

10. Медиа-запросы без дублирования

Sass позволяет держать медиа-запрос внутри селектора, а не копировать селекторы вниз файла:

.menu {
  display: flex;

  @media (max-width: 768px) {
    display: none;
  }
}
// Спокойнее: вся логика одного блока в одном месте.
Подход «mobile-first»: пиши базовые стили, затем @media (min-width: ...) для расширения. На мобильной эмуляции в Chrome DevTools это удобно проверять вживую.

11. Практика: мини-проект

Собери кнопку с состояниями и карточку в осмысленной структуре:

// _settings.scss
$primary: #2563eb;
$radius: 10px;
$space-sm: 8px;
$space-md: 16px;

// _components.scss
@use "settings";

.card {
  border: 1px solid #e5e7eb;
  border-radius: settings.$radius;
  padding: settings.$space-md;

  &__title { font-weight: 600; }
  &__body  { margin-top: settings.$space-sm; }
}

.btn {
  display: inline-block;
  padding: settings.$space-sm settings.$space-md;
  border-radius: settings.$radius;
  background: settings.$primary;
  color: #fff;

  &:hover { opacity: .9; }
  &:focus-visible { outline: 2px solid #93c5fd; }
}

Компонентная структура блоков по БЭМ и переменные из одного файла-настроек — стандарт промышленной вёрстки.

12. Sass и переменные против CSS Custom Properties

Переменные Sass работают на этапе компиляции: они подставляются в готовый CSS и недоступны браузеру. CSS Custom Properties (--var) живут в рантайме и могут меняться из JavaScript и медиа-запросов.

// переменные Sass — значения на этапе сборки
$color-primary: #2563eb;

// CSS-переменные — динамика в браузере
:root {
  --color-accent: #f59e0b;
}

.toggle--dark .theme {
  --color-accent: #fbbf24;
  color: var(--color-accent);
}
КритерийSass-переменнаяCSS-переменная
время жизниво время компиляциив браузере
меняется из JSнетда
подходит дляпалитра, сетка, препроцессортемы, стили компонентов
в итоговом CSSне остаётсяостаётся
Совмещайте: Sass-переменные задают дефолтные значения, а на компоненте они «перетекают» в CSS Custom Properties для темной темы и интерактива.

13. Структура проекта с @use

В промышленных проектах стили разделяют по файлам. Классический паттерн — «7-1»: abstracts, base, components, layout, pages, themes, vendors.

scss/
├─ abstracts/   _variables.scss _mixins.scss
├─ base/        _reset.scss _typography.scss
├─ components/  _buttons.scss _cards.scss
├─ layout/      _header.scss _footer.scss
├─ pages/       _home.scss
├─ themes/      _dark.scss
├─ vendors/     _bootstrap.scss
└─ main.scss
// main.scss — единственная точка входа
@use "abstracts/variables";
@use "abstracts/mixins";
@use "base/reset";
@use "base/typography";
@use "components/buttons";
@use "layout/header";
Порядок @use важен: частичные файлы подключаются один раз, а имена неймспейсов (variables.$color) делают зависимости явными.

14. Тёмная тема и переключение стилей

Соберём переключение темы на CSS-переменных, которые наполняются из Sass-настроек. Тёмная тема переопределяет только цвета, а вся вёрстка остаётся общей.

// tokens.scss
$colors-light: (bg: #ffffff, text: #111827);
$colors-dark:  (bg: #111827, text: #f9fafb);

:root[data-theme="light"] {
  --bg: #{map.get($colors-light, "bg")};
  --text: #{map.get($colors-light, "text")};
}
:root[data-theme="dark"] {
  --bg: #{map.get($colors-dark, "bg")};
  --text: #{map.get($colors-dark, "text")};
}
.app {
  background: var(--bg);
  color: var(--text);
}
Не меняйте темы через Sass-переменные — итоговый CSS статичен. Тема обязана жить в CSS Custom Properties, иначе переключение в рантайме невозможно.

15. Source maps и отладка

Source maps связывают скомпилированный CSS с исходными файлами .scss: в DevTools вы видите не готовую кашу, а точную строку оригинального Sass. Без карт отладка превращается в поиск в двух файлах сразу.

// генерируем карты при компиляции
npx sass --watch --source-map styles.scss styles.css

// рядом появится styles.css.map

В Vite карты включаются автоматически в dev-режиме. Помогает и сама консоль Sass: @debug печатает значение, @warn предупреждает, а @error останавливает компиляцию.

$gutter: 12px;

@debug $gutter;                      // main.scss:2 DEBUG: 12px
@warn "Переменная $gutter устарела";
@if not $gutter { @error "Нет отступов"; }
Если в DevTools в списке файлов нет .scss — проверьте, что сборщик подключает карты и не выбрасывает их в продакшен-режиме.

16. Встроенные модули: sass:math и sass:color

Современный Sass выносит расчёты в модули. Классическое деление / в новых версиях уступает math.div(), а устаревшие lighten()/darken() заменяет color.adjust().

@use "sass:math";
@use "sass:color";

$primary: #2563eb;

.title { font-size: math.div(24px, 16px) * 1rem; }     // 1.5rem
.btn   { background: color.adjust($primary, $lightness: 8%); }
.line  { border-color: color.mix(#fff, $primary, 25%); }
.rounded { width: math.round(14.7px); }                // 15px

Модули sass:map и sass:list играют вместе с циклами из раздела 9: map.merge() объединяет словари брейкпоинтов, map.get() достаёт значение, а math.min()/math.max() ограничивают размеры.

@use "sass:map";

$bp: (sm: 576px, md: 768px, lg: 992px);
$bp: map.merge($bp, (xl: 1200px));

@media (min-width: map.get($bp, xl)) {
  .g--xl { display: grid; }
}
Подключайте модули через @use "sass:..." и вызывайте функции с префиксом модуля: так понятно, откуда пришла функция, и проще избежать конфликтов с собственными именами.

17. Тонкая настройка вывода: %placeholder и @extend

Плейсхолдер %имя — селектор-призрак: он не попадает в итоговый CSS, пока его не унаследуют. А @extend объединяет селекторы в одно правило и делает вывод компактнее.

%card-base {
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  padding: 16px;
}

.form-card    { @extend %card-base; }
.profile-card { @extend %card-base; }
// Итог: один блок вместо двух копий
.form-card, .profile-card {
  border: 1px solid #e5e7eb;
  border-radius: 10px;
  padding: 16px;
}

Чем это отличается от миксина из раздела 6? Миксин копирует свойства в каждое место, поэтому итоговый CSS больше, зато селекторы независимы. @extend экономит байты, но создаёт групповые селекторы, которые труднее переопределять на маленьких экранах.

Не пользуйтесь @extend внутри @media, не расширяйте «живые» классы библиотек и вложенные БЭМ-элементы — компилятор предупредит, а правила станут хрупкими.

18. Stylelint, pre-commit и CI

Стили линтуют так же, как JavaScript. stylelint с SCSS-конфигом ловит лишнюю вложенность, разнобой в кавычках и «мёртвые» переменные ещё до ревью.

// .stylelintrc.json
{
  "extends": ["stylelint-config-standard-scss"],
  "rules": { "color-hex-length": "short" }
}

// package.json
"scripts": {
  "lint:css": "stylelint \"**/*.scss\""
}

Связка husky + lint-staged прогоняет линтер только по изменённым файлам перед коммитом, а в CI (например, GitHub Actions) задача выполняется на каждый пуш — «у меня всё работает» больше не аргумент.

КонфигФилософияКогда брать
standard-scssофициальный «стандарт»проект с нуля
sass-guidelinesправила от Хьюго Жирарделякрупные команды
stylelint-high-performanceзапрет «дорогих» свойствсложные интерфейсы
Вводите правила постепенно: сначала авто-исправление --fix, затем спорные правила обсуждайте на ревью и только потом включайте жёсткую проверку в CI.

19. Оптимизация сборки CSS: минификация и purge

Sass пишет удобный для разработчика код — но именно на продакшн CSS должен прийти компактным. Современные сборщики делают три вещи: автопрефиксы, минификацию и удаление неиспользуемых стилей (purge).

// vite.config.js
css: { postcss: { plugins: [ autoprefixer() ] } }
// команда сборки
npm run build  →  dist/assets/*.css (минифицированный)
// purgecss.config.js
content: ['./src/**/*.html', './src/**/*.js']
defaultExtractor: content => content.match(/[A-Za-z0-9_-]+/g) || []
Purge удаляет классы, которых не найдёт в шаблонах. Динамические классы, собираемые из строк ("icon-" + name), надо перечислять в safelist иначе стили исчезнут на проде.
Сравнивайте размер до и после: если purge ничего не отрезал — проверьте, что content-массив реально указывает на все шаблоны.

20. Современный CSS против Sass

CSS развивается быстрее, чем раньше: нативные переменные, вложенность, @layer и color(): функции. Иногда препроцессор уже не нужен. Считаем честно, когда что использовать.

ВозможностьНативный CSSSass
вложенностьда (c 2023, с ограничениями)да, без ограничений
переменныеcustom propertiesкомпилируются в значения
миксинынетда
условия/циклынетда

При вложенности в нативном CSS помните: селекторы & работают, но без сасс-переменных. Custom properties живут при рантайме — их можно менять в JS, преимущество для тем (см. раздел 14).

.card {
  --pad: 1rem;
  padding: var(--pad);
  &.active { border-color: gold; }
}

21. Дизайн-система: токены и утилиты

Крупный интерфейс не может жить на «магических» числах. Дизайн-система — это источник правды: токены (отступы, цвета, типографика) задаются один раз и переиспользуются.

$spacing-base: 8px;
// отступы растут кратно базе
$sp-1: $spacing-base; $sp-2: $spacing-base * 2; $sp-3: $spacing-base * 3;

$color-bg: #121420;
$color-accent: #ff6b35;
$color-text: #e8e8f0;

Хорошая практика — миксин для типографики и fluid spacing через clamp():

@mixin fluid-type($min, $max) {
  font-size: clamp(#{$min}px, #{$min}px + 1vw, #{$max}px);
}
.title-brand { @include fluid-type(20, 42); }
См. курс CSS: токены — это шаг «переменных» на уровень архитектуры всего проекта, а не одной страницы.

22. Публикация библиотеки стилей (SCSS как npm-пакет)

Когда кнопки, отступы и миксины вы готовы переиспользовать в нескольких проектах, самое время опубликовать дизайн-пакет. Публичный репозиторий, npm-пакет с sass в exports — и команда тянет @import 'design-kit' отовсюду.

// package.json дизайн-пакета
{
  "exports": { ".": "./src/index.scss" },
  "scripts": { "build": "sass src/index.scss dist/design.css" }
}
npm install @team/design-kit
 
@use '@team/design-kit' as kit;
.btn { @include kit.button-size('lg'); }
Не тащите в дизайн-пакет стили конкретных страниц — пакет должен оставаться независимым от приложений, которые его используют.
Versioning через git-теги и tag_releases позволяет командам на разных проектах подписываться на нужную версию без конфликтов.

Что дальше

Стили на Sass соединятся с разметкой из курса HTML и базовых стилей CSS. В Vue 3 и React Sass подключается парой строк, а сборку ускорит Vite. Проверить результат можно аудитом Lighthouse.

← Вернуться к курсам