본문으로 건너뛰기

SCSS 스타터 템플릿

프로젝트 시작 시 아래 구조를 기본 템플릿으로 가져가면 퍼블리싱 속도와 일관성을 같이 챙기기 좋습니다.

추천 구조

styles/
abstracts/
_variables.scss
_functions.scss
_mixins.scss
_breakpoints.scss
base/
_reset.scss
_typography.scss
_a11y.scss
components/
_button.scss
_input.scss
_card.scss
layout/
_header.scss
_footer.scss
pages/
_home.scss
main.scss

1. _variables.scss

$color-primary: #2563eb;
$color-primary-dark: #1d4ed8;
$color-text: #111827;
$color-subtext: #6b7280;
$color-border: #d1d5db;
$color-bg: #ffffff;
$color-surface: #f9fafb;
$color-danger: #dc2626;
$color-success: #16a34a;

$space-4: 4px;
$space-8: 8px;
$space-12: 12px;
$space-16: 16px;
$space-20: 20px;
$space-24: 24px;
$space-32: 32px;

$radius-sm: 6px;
$radius-md: 10px;
$radius-lg: 16px;
$radius-full: 9999px;

$shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
$shadow-md: 0 6px 18px rgba(0, 0, 0, 0.12);

$motion-duration-fast: 150ms;
$motion-duration-base: 200ms;
$motion-duration-slow: 300ms;
$motion-ease: ease-out;

2. _breakpoints.scss

$breakpoints: (
sm: 576px,
md: 768px,
lg: 1024px,
xl: 1280px,
);

3. _functions.scss

@use 'sass:math';

@function rem($px, $base: 16) {
@return math.div($px, $base) * 1rem;
}

@function em($px, $base: 16) {
@return math.div($px, $base) * 1em;
}

4. _mixins.scss

@use './breakpoints' as *;

@mixin mq($point) {
@media (min-width: map-get($breakpoints, $point)) {
@content;
}
}

@mixin flex-center($inline: false) {
display: if($inline, inline-flex, flex);
align-items: center;
justify-content: center;
}

@mixin ellipsis($line: 1) {
overflow: hidden;

@if $line == 1 {
white-space: nowrap;
text-overflow: ellipsis;
} @else {
display: -webkit-box;
-webkit-line-clamp: $line;
-webkit-box-orient: vertical;
}
}

@mixin focus-ring($color: rgba(37, 99, 235, 0.35)) {
outline: none;
box-shadow: 0 0 0 3px $color;
}

@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: 0;
}

@mixin button-base {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 44px;
padding: 0 16px;
border: 1px solid transparent;
border-radius: 10px;
font-weight: 600;
transition:
background-color 200ms ease-out,
border-color 200ms ease-out,
color 200ms ease-out,
box-shadow 200ms ease-out,
transform 150ms ease-out;
}

5. _a11y.scss

button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
@include focus-ring;
}

.sr-only {
@include visually-hidden;
}

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}

6. _button.scss

.button {
@include button-base;
background: $color-primary;
color: #fff;

&:hover {
background: $color-primary-dark;
}

&:focus-visible {
@include focus-ring;
}

&:disabled,
&.is-disabled {
opacity: 0.5;
cursor: not-allowed;
}

&--ghost {
background: transparent;
color: $color-primary;
border-color: $color-primary;
}

&--sm {
min-height: 36px;
padding: 0 12px;
}

&--lg {
min-height: 52px;
padding: 0 20px;
}
}

7. main.scss

@use './abstracts/variables' as *;
@use './abstracts/functions' as *;
@use './abstracts/breakpoints' as *;
@use './abstracts/mixins' as *;

@use './base/reset';
@use './base/typography';
@use './base/a11y';

@use './components/button';
@use './components/input';
@use './components/card';

적용 팁

  • 공통 스타일은 먼저 템플릿에 추가하고 페이지 작업을 시작합니다.
  • 새로 반복되는 패턴이 보이면 컴포넌트에 복붙하기보다 mixin/function으로 먼저 분리합니다.
  • 상태 클래스는 .is-active, .is-open, .has-error처럼 일관되게 맞춥니다.
  • 효과 값은 컴포넌트별 개별 지정보다 공통 토큰을 우선 사용합니다.
  • 템플릿은 최소한으로 시작하고, 프로젝트 특성에 맞춰 확장합니다.

함께 보면 좋은 문서