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처럼 일관되게 맞춥니다. - 효과 값은 컴포넌트별 개별 지정보다 공통 토큰을 우선 사용합니다.
- 템플릿은 최소한으로 시작하고, 프로젝트 특성에 맞춰 확장합니다.