SCSS는 사실상 프로그래밍 언어에 가깝다. 변수와 중첩부터 반복문, 조건문, 함수까지 자주 쓰는 문법을 정리했다.

1. 변수.

$로 선언한다. CSS의 커스텀 프로퍼티(--)와 달리 컴파일 시점에 값으로 치환되어 박히므로, 런타임에 바꿀 수는 없다.

$main-color: #3b82f6;
$gap: 16px;

.button {
	background: $main-color;
	padding: $gap;
}

2. 중첩과 부모 참조.

선택자를 중첩하면 자동으로 자손(공백)으로 컴파일된다. &는 부모 선택자를 가리키는데, 뒤에 공백을 두면 결국 자손이 되어 & 없이 쓴 것과 똑같아진다. 반대로 공백 없이 붙이면 부모와 같은 요소를 겨냥하거나 이름을 이어 붙이므로, 이때는 &가 반드시 필요하다.

.card {
	padding: 16px;

	/* 중첩만 해도 자손이 된다 → .card .title */
	.title {
		font-weight: bold;
	}

	/* & 뒤에 공백을 두면 위와 똑같은 .card .title 이라 굳이 쓸 필요가 없다 */
	& .title {}

	/* 공백 없이 붙이면 부모와 같은 요소를 겨냥한다 → .card:hover */
	&:hover {
		background: #f5f5f5;
	}

	/* 이름을 이어 붙인다 → .card--active */
	&--active {
		border: 1px solid;
	}
}

3. 믹스인.

재사용할 스타일 묶음이다. 인자와 기본값을 받을 수 있다.

@mixin flex-center($direction: row) {
	display: flex;
	justify-content: center;
	align-items: center;
	flex-direction: $direction;
}

.box {
	@include flex-center(column);
}

4. 함수.

믹스인이 스타일 덩어리를 반환한다면, 함수는 값을 계산해 반환한다.

@function double($n) {
	@return $n * 2;
}

.box {
	padding: double(8px); /* 16px */
}

5. 반복문.

셋 다 반복이지만 쓰임이 다르다. @for는 정해진 횟수만큼 숫자를 셀 때, @each는 미리 정해둔 목록(이름, 색상 등)을 훑을 때, @while은 종료 조건이 숫자 범위로 딱 떨어지지 않을 때 쓴다.

/* @for: 1부터 3까지 센다 (through는 끝값 3 포함, to로 쓰면 3 제외) */
@for $i from 1 through 3 {
	.col-#{$i} {
		width: calc(100% / #{$i});
	}
}
/* 결과:
   .col-1 { width: calc(100% / 1); }
   .col-2 { width: calc(100% / 2); }
   .col-3 { width: calc(100% / 3); } */

/* @each: 맵의 항목을 이름과 값으로 하나씩 꺼낸다 */
@each $name, $color in ("info": blue, "warn": orange) {
	.text-#{$name} {
		color: $color;
	}
}
/* 결과:
   .text-info { color: blue; }
   .text-warn { color: orange; } */

/* @while: 조건이 참인 동안 반복한다. 카운터를 직접 증가시켜야 한다 */
$i: 1;
@while $i <= 3 {
	.m-#{$i} { margin: #{$i * 4}px; }
	$i: $i + 1;
}
/* 결과:
   .m-1 { margin: 4px; }
   .m-2 { margin: 8px; }
   .m-3 { margin: 12px; } */

6. 조건문.

넘긴 인자에 따라 어느 가지를 내보낼지 컴파일 시점에 고른다. 참인 가지의 스타일만 최종 CSS에 남는다. @else if로 여러 조건을, @else로 나머지 경우를 처리한다.

@mixin theme($mode) {
	@if $mode == dark {
		background: #111;
		color: #eee;
	} @else if $mode == sepia {
		background: #f4ecd8;
		color: #5b4636;
	} @else {
		background: #fff;
		color: #111;
	}
}

.page {
	@include theme(dark);
}
/* dark 가지가 참이라 .page 에는 그 스타일만 박힌다:
   .page { background: #111; color: #eee; } */

7. 보간.

값이 오는 자리(속성 값)에서는 그냥 $변수로 쓰면 된다. 하지만 선택자 이름이나 속성 이름처럼 변수를 "코드의 일부"로 끼워 넣어야 할 때는 SCSS가 그게 변수인 줄 모르므로, #{}로 감싸 "여기에 값을 넣어라"라고 알려줘야 한다. calc()는 CSS가 실행 시점에 계산하는 함수라 안쪽을 문자열로 넘기는데, 이때 SCSS 변수도 문자 그대로 남으므로 마찬가지로 보간해 값을 미리 넣어줘야 한다.

$side: left;
$gap: 16px;

.menu {
	/* 값 자리에서는 보간이 필요 없다 → padding: 16px */
	padding: $gap;

	/* 속성 이름에 끼워 넣을 땐 보간해야 한다 → margin-left: 20px */
	margin-#{$side}: 20px;

	/* 보간 없이 margin-$side 라고 쓰면 값이 안 들어가고 문자 그대로 깨진다 */

	/* calc 안의 SCSS 변수도 보간해야 값이 들어간다 → width: calc(100% - 16px) */
	width: calc(100% - #{$gap});
}

8. 파일 분리와 @use.

큰 스타일을 여러 파일로 쪼갤 때, 다른 파일에서 불러다 쓸 조각은 이름을 밑줄로 시작한다. 밑줄은 컴파일러에게 "이 파일은 단독으로 쓰는 게 아니니 _colors.css 같은 결과 파일을 따로 만들지 말라"고 알려주는 표시다. 밑줄이 없으면 colors.scss는 그 자체로 colors.css까지 만들어내 불필요한 파일이 생긴다.

그리고 불러올 때는 밑줄과 .scss 확장자를 뺀 이름으로 쓴다. 즉 파일이 _colors.scss여도 @use "colors"라고 적으면 컴파일러가 알아서 _colors.scss를 찾아 연결한다. 이렇게 불러오면 파일 이름(colors)이 네임스페이스가 되어 접두어를 붙여 접근하고, 접두어가 번거로우면 as *로 생략할 수 있다.

/* 파일명: _colors.scss (밑줄로 시작 → 단독 .css로 컴파일되지 않는다) */
$primary: #3b82f6;
/* ── 네임스페이스로 쓰는 경우 ── */
@use "colors"; /* _colors.scss 를 밑줄·확장자 없이 부른다. colors 가 네임스페이스가 된다 */

.button {
	color: colors.$primary; /* 접두어를 붙여 접근한다 */
}

/* ── 네임스페이스를 생략하는 경우 ── */
@use "colors" as *;

.button {
	color: $primary; /* 접두어 없이 바로 쓴다 */
}

9. 상속과 자리표시자.

@extend로 다른 선택자의 스타일을 물려받는다. %로 시작하는 자리표시자는 단독으로 출력되지 않고, 상속될 때만 실제 스타일로 나타난다.

%card-base {
	border-radius: 8px;
	padding: 16px;
}

.notice {
	@extend %card-base;
	background: #eef;
}
/* 결과: .notice 는 물려받은 속성과 자기 속성을 모두 갖는다
   .notice {
     border-radius: 8px;
     padding: 16px;
     background: #eef;
   } */

* 참조
Sass 공식 문서 (sass-lang.com)