Skip to main content

스타일링: CSS Modules와 Tailwind를 디자인 토큰으로 이해하기

Views 90약 9분생성형 AI 활용
Summary

오늘의 목표: 스타일이 어디에서 관리되는지 보기

지난 장에서는 포트폴리오 첫 화면을 만들었습니다.

구조는 이미 있습니다.

main.page
├─ Hero Section
├─ About Section
└─ Projects Section
   ├─ Project Card
   └─ Project Card

이번 장에서는 이 구조에 입혀진 스타일을 더 체계적으로 이해합니다.

디자이너에게 스타일링은 낯선 일이 아닙니다. Figma에서도 매일 하고 있습니다.

색상 선택
글꼴 지정
간격 맞추기
오토레이아웃 설정
반응형 프레임 확인
컴포넌트 variant 만들기

웹 코드에서도 같은 일을 합니다. 다만 도구의 이름이 바뀝니다.

Figma Color Style      → CSS color 또는 Tailwind 색상 클래스
Figma Text Style       → font-size, line-height, font-weight
Figma Spacing Token    → padding, margin, gap
Figma Auto Layout      → flex, grid
Figma Constraints      → width, max-width, media query
Figma Component Variant → class 조합 또는 조건부 스타일

이번 장의 핵심은 문법을 많이 외우는 것이 아닙니다.

디자인 결정이 코드에서 어디에 저장되고, 어떻게 반복되고, 언제 깨지는지 보는 눈을 갖는 것입니다.

Next.js 공식 학습 코스에서도 Next.js 앱을 스타일링하는 대표적인 방식으로 전역 CSS, Tailwind CSS, CSS Modules를 소개합니다. Tailwind와 CSS Modules는 둘 중 하나만 써야 하는 방식이 아니라, 프로젝트에 따라 함께 사용할 수도 있습니다.1

스타일링: CSS Modules와 Tailwind를 디자인 토큰으로 이해하기

먼저 구분하기: 전역 스타일과 로컬 스타일

지난 장에서 우리는 app/globals.css에 스타일을 넣었습니다.

이름 그대로 globals.css는 전역 스타일입니다.

전역 스타일은 사이트 전체에 깔리는 기본 규칙입니다.

globals.css
├─ body 배경색
├─ 기본 글꼴
├─ 전체 box-sizing
├─ h1, h2, p의 기본 여백
└─ 사이트 전체에서 공유하는 기본 스타일

Figma로 비유하면 팀 전체가 공유하는 스타일 가이드에 가깝습니다.

Figma Team Library
├─ Color Styles
├─ Text Styles
└─ Effect Styles

전역 스타일은 편리합니다. 하지만 모든 곳에 영향을 줍니다.

예를 들어 h1 스타일을 전역으로 바꾸면 사이트 안의 모든 h1이 바뀔 수 있습니다.

h1 {
  font-size: 48px;
}

이것은 Figma에서 모든 Heading/Large 텍스트 스타일을 수정하는 것과 비슷합니다. 의도했다면 좋지만, 특정 화면만 바꾸고 싶었는데 전체가 바뀌면 문제가 됩니다.

그래서 스타일은 보통 두 층으로 나눠서 생각합니다.

전역 스타일
→ 사이트 전체의 기본값과 디자인 토큰

로컬 스타일
→ 특정 페이지나 특정 컴포넌트의 모양

이번 장에서 배울 CSS Modules는 이 두 번째 영역, 즉 로컬 스타일을 다루는 방법입니다.

CSS Modules: 특정 컴포넌트 안에서만 작동하는 스타일

CSS Modules는 쉽게 말해 이 컴포넌트에만 적용되는 CSS 파일입니다.

보통 CSS는 클래스 이름이 같으면 서로 영향을 줄 수 있습니다.

.card {
  padding: 24px;
}

다른 파일에서도 .card라는 이름을 쓰면 충돌할 수 있습니다.

ProjectCard의 .card
ArticleCard의 .card
PricingCard의 .card

사람이 보기에는 모두 card입니다. 하지만 각각 원하는 모양이 다를 수 있습니다.

CSS Modules는 이 문제를 줄여 줍니다. .module.css 파일에 쓴 클래스 이름을 Next.js가 내부적으로 고유한 이름으로 바꿔 주기 때문입니다. 공식 학습 코스에서도 CSS Modules는 컴포넌트 단위로 CSS 범위를 제한하고, 자동으로 고유한 클래스 이름을 만들어 스타일 충돌을 막는 방식이라고 설명합니다.1

디자이너 관점에서는 이렇게 이해하면 됩니다.

일반 CSS 클래스
→ 팀 파일 전체에 같은 이름으로 보이는 레이어 이름

CSS Modules 클래스
→ 특정 컴포넌트 안에서만 의미가 있는 레이어 이름

Figma 컴포넌트 안에 Title, Description, Thumbnail이라는 레이어가 있을 수 있습니다. 다른 컴포넌트 안에도 똑같은 이름의 Title 레이어가 있을 수 있습니다.

하지만 그것이 곧바로 충돌하지는 않습니다. 각 컴포넌트 안에서만 해석되기 때문입니다.

CSS Modules도 비슷합니다.

ProjectCard.module.css의 .title
Hero.module.css의 .title

둘 다 title이지만 서로 다른 영역의 title

실습 1: 포트폴리오 페이지를 CSS Modules로 정리하기

지난 장에서는 app/globals.css에 많은 스타일을 넣었습니다.

이번에는 페이지 전용 스타일을 app/page.module.css로 옮겨 보겠습니다.

폴더 구조는 이렇게 됩니다.

app
├─ page.tsx
├─ page.module.css
├─ layout.tsx
└─ globals.css

globals.css에는 전체 기본값만 남깁니다.

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: #f7f3ee;
  color: #1f1f1f;
  font-family: Arial, sans-serif;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

그리고 app/page.module.css 파일을 새로 만듭니다.

.page {
  width: min(1080px, calc(100% - 40px));
  margin: 0 auto;
  padding: 64px 0;
}

.heroSection {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 40px;
  align-items: center;
  padding: 56px;
  border-radius: 32px;
  background: #ffffff;
}

.eyebrow {
  margin: 0 0 12px;
  color: #7a5cff;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.heroTitle {
  max-width: 720px;
  font-size: 48px;
  line-height: 1.08;
  letter-spacing: -0.04em;
}

.heroText {
  max-width: 640px;
  color: #4b4b4b;
  font-size: 18px;
  line-height: 1.7;
}

.profileImage {
  border-radius: 50%;
  object-fit: cover;
}

.section {
  margin-top: 32px;
  padding: 48px;
  border-radius: 32px;
  background: #ffffff;
}

.sectionTitle {
  max-width: 760px;
  font-size: 32px;
  line-height: 1.2;
  letter-spacing: -0.03em;
}

.sectionText {
  color: #4b4b4b;
  font-size: 18px;
  line-height: 1.7;
}

.projectGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
  margin-top: 24px;
}

.projectCard {
  overflow: hidden;
  border: 1px solid #e7e0d8;
  border-radius: 24px;
  background: #fffdf9;
}

.projectImage {
  width: 100%;
  height: auto;
  display: block;
}

.projectContent {
  padding: 24px;
}

.projectTitle {
  margin-bottom: 8px;
  font-size: 22px;
}

.projectDescription {
  margin-bottom: 0;
  color: #4b4b4b;
  font-size: 16px;
  line-height: 1.6;
}

@media (max-width: 760px) {
  .heroSection {
    grid-template-columns: 1fr;
    padding: 32px;
  }

  .heroTitle {
    font-size: 36px;
  }

  .section {
    padding: 32px;
  }

  .sectionTitle {
    font-size: 26px;
  }

  .projectGrid {
    grid-template-columns: 1fr;
  }
}

이제 app/page.tsx에서 이 CSS 파일을 불러옵니다.

import Image from "next/image";
import styles from "./page.module.css";

그리고 기존의 문자열 클래스 이름을 styles.이름 형태로 바꿉니다.

<main className={styles.page}>
  <section className={styles.heroSection}>
    <div>
      <p className={styles.eyebrow}>Product Designer Portfolio</p>
      <h1 className={styles.heroTitle}>
        사용자의 문제를 화면의 구조로 바꾸는 디자이너, 민지입니다.
      </h1>
      <p className={styles.heroText}>
        저는 리서치에서 발견한 문제를 정보 구조, 사용자 흐름, 인터페이스로 연결하는 일을 합니다.
      </p>
    </div>

    <Image
      src="/profile.jpg"
      alt="프로덕트 디자이너 민지의 프로필 사진"
      width={160}
      height={160}
      className={styles.profileImage}
    />
  </section>
</main>

처음 보면 className="page"보다 className={styles.page}가 더 복잡해 보입니다.

하지만 의미는 분명합니다.

className="page"
→ 전역에서 page라는 이름을 찾는다

className={styles.page}
→ 이 파일이 import한 CSS Module 안에서 page를 찾는다

Figma로 바꾸면 이런 차이입니다.

전역 className
→ 팀 전체 라이브러리에서 같은 이름의 스타일 찾기

CSS Modules
→ 이 컴포넌트 옆에 붙어 있는 전용 스타일 찾기

CSS Modules는 이런 경우에 특히 좋습니다.

- 특정 컴포넌트의 세부 스타일이 많을 때
- JSX 안에 클래스 이름이 너무 길게 들어가는 것이 싫을 때
- 전통적인 CSS 문법으로 차분하게 스타일을 관리하고 싶을 때
- 스타일 충돌을 줄이고 싶을 때

디자인 토큰: 숫자와 색상에 이름을 붙이는 일

지금 CSS에는 숫자와 색상이 직접 들어 있습니다.

padding: 56px;
border-radius: 32px;
background: #ffffff;
color: #7a5cff;

작은 프로젝트에서는 괜찮습니다. 하지만 화면이 많아지면 문제가 생깁니다.

어떤 화면은 24px
어떤 화면은 25px
어떤 화면은 #7a5cff
어떤 화면은 #7658ff

눈으로 보면 거의 비슷하지만, 코드에서는 모두 다른 값입니다.

디자인 시스템에서는 이런 문제를 줄이기 위해 토큰을 씁니다.

color.brand.primary = #7a5cff
color.surface.default = #ffffff
space.6 = 24px
radius.lg = 24px

코드에서도 비슷한 방식으로 CSS 변수를 만들 수 있습니다.

app/globals.css에 토큰처럼 사용할 값을 정의해 봅니다.

:root {
  --color-bg: #f7f3ee;
  --color-surface: #ffffff;
  --color-surface-soft: #fffdf9;
  --color-text: #1f1f1f;
  --color-text-muted: #4b4b4b;
  --color-brand: #7a5cff;
  --color-border: #e7e0d8;

  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-14: 56px;
  --space-16: 64px;

  --radius-lg: 24px;
  --radius-xl: 32px;
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: Arial, sans-serif;
}

이제 page.module.css에서는 직접 색상 값을 쓰는 대신 토큰을 부릅니다.

.heroSection {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-10);
  align-items: center;
  padding: var(--space-14);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
}

.eyebrow {
  margin: 0 0 var(--space-3);
  color: var(--color-brand);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

이렇게 하면 디자인 결정이 한 곳에 모입니다.

브랜드 컬러를 바꿔야 한다
→ --color-brand만 수정

카드 radius를 바꿔야 한다
→ --radius-lg 또는 --radius-xl 수정

간격 체계를 조정해야 한다
→ --space-* 값 확인

디자이너에게 중요한 지점은 여기입니다.

개별 화면의 픽셀값을 하나씩 맞추는 것보다, 팀이 공유하는 토큰 이름과 규칙을 맞추는 것이 더 중요합니다.

Figma의 변수나 스타일과 코드의 토큰 이름이 연결되면 핸드오프가 훨씬 선명해집니다.

Figma variable: color/brand/primary
Code token: --color-brand

Figma spacing: spacing/6
Code token: --space-6

이름이 같거나 대응표가 있으면 디자이너와 개발자가 같은 언어로 이야기할 수 있습니다.

Tailwind CSS: 토큰을 짧은 조각으로 조합하는 방식

이번에는 Tailwind CSS를 보겠습니다.

Tailwind는 CSS 파일에 긴 규칙을 따로 쓰기보다, JSX 안에 작은 유틸리티 클래스를 조합해서 스타일을 만드는 방식입니다. 공식 학습 코스에서는 Tailwind를 React 코드 안에 유틸리티 클래스를 직접 붙여 빠르게 스타일링할 수 있게 해 주는 CSS 프레임워크로 설명합니다.1

예를 들어 CSS Modules에서는 이렇게 씁니다.

.projectCard {
  overflow: hidden;
  border: 1px solid #e7e0d8;
  border-radius: 24px;
  background: #fffdf9;
}

Tailwind 방식으로는 JSX 안에 이렇게 씁니다.

<article className="overflow-hidden rounded-3xl border border-stone-200 bg-stone-50">
  ...
</article>

처음 보면 HTML 안이 지저분해 보일 수 있습니다. 하지만 관점을 바꾸면 다르게 보입니다.

Tailwind 클래스는 하나하나가 작은 디자인 토큰 호출입니다.

rounded-3xl       → 큰 radius
border            → 1px border
border-stone-200  → 연한 회색 border
bg-stone-50       → 아주 밝은 배경색
p-6               → padding scale 6
gap-6             → gap scale 6
text-sm           → 작은 글자 크기
font-bold         → 굵은 글자

Figma로 비유하면, 오른쪽 패널에서 속성을 하나씩 고르는 대신 레이어 이름 옆에 속성 칩을 붙이는 느낌입니다.

Project Card
[overflow-hidden] [rounded-3xl] [border] [bg-stone-50] [p-6]

Tailwind의 장점은 빠른 조합입니다.

카드에 padding 추가 → p-6
간격 추가 → gap-6
모바일에서 1열, 데스크톱에서 2열 → grid-cols-1 md:grid-cols-2
글자색 변경 → text-stone-600

단점도 있습니다.

클래스가 길어지면 JSX가 복잡해 보입니다. 그리고 팀의 디자인 토큰과 Tailwind의 기본 scale이 맞지 않으면, 디자이너가 의도한 값과 개발자가 고른 값이 어긋날 수 있습니다.

그래서 Tailwind를 쓸 때도 중요한 질문은 같습니다.

우리 팀의 색상, 간격, radius, 타이포그래피 토큰이
Tailwind 설정과 어떻게 연결되어 있는가?

Hero, ProjectCard, Button을 두 방식으로 바라보기

같은 화면도 CSS Modules 방식과 Tailwind 방식으로 표현할 수 있습니다.

어느 하나가 무조건 정답은 아닙니다. 팀의 취향, 프로젝트 규모, 디자인 시스템의 성숙도에 따라 달라집니다.

Hero 섹션

CSS Modules 방식은 스타일 이름을 먼저 정합니다.

<section className={styles.heroSection}>
  <div>
    <p className={styles.eyebrow}>Product Designer Portfolio</p>
    <h1 className={styles.heroTitle}>사용자의 문제를 화면의 구조로 바꾸는 디자이너</h1>
  </div>
</section>

이 방식은 읽을 때 구조가 깔끔합니다.

heroSection
eyebrow
heroTitle

Figma 레이어 이름처럼 보입니다. 대신 실제 padding, gap, background 값을 보려면 CSS 파일을 열어야 합니다.

Tailwind 방식은 값이 JSX 안에 바로 보입니다.

<section className="grid items-center gap-10 rounded-[32px] bg-white p-14 md:grid-cols-[1fr_auto]">
  <div>
    <p className="mb-3 text-sm font-bold uppercase tracking-wide text-violet-500">
      Product Designer Portfolio
    </p>
    <h1 className="max-w-3xl text-5xl font-bold leading-tight tracking-tight">
      사용자의 문제를 화면의 구조로 바꾸는 디자이너
    </h1>
  </div>
</section>

이 방식은 속성이 바로 보입니다. 대신 클래스가 길어지면 처음 읽는 사람에게 부담이 됩니다.

ProjectCard

프로젝트 카드는 반복되는 컴포넌트입니다.

Figma에서는 Project Card 컴포넌트를 만들고, 각 인스턴스의 제목과 이미지를 바꿉니다.

코드에서도 같은 감각으로 볼 수 있습니다.

CSS Modules 방식:

<article className={styles.projectCard}>
  <Image
    src={project.image}
    alt={`${project.title} 썸네일`}
    width={640}
    height={400}
    className={styles.projectImage}
  />
  <div className={styles.projectContent}>
    <h3 className={styles.projectTitle}>{project.title}</h3>
    <p className={styles.projectDescription}>{project.description}</p>
  </div>
</article>

Tailwind 방식:

<article className="overflow-hidden rounded-3xl border border-stone-200 bg-stone-50">
  <Image
    src={project.image}
    alt={`${project.title} 썸네일`}
    width={640}
    height={400}
    className="block h-auto w-full"
  />
  <div className="p-6">
    <h3 className="mb-2 text-xl font-bold">{project.title}</h3>
    <p className="mb-0 text-base leading-relaxed text-stone-600">
      {project.description}
    </p>
  </div>
</article>

디자이너가 읽기에는 CSS Modules가 더 컴포넌트 이름 중심입니다.

projectCard
projectImage
projectContent
projectTitle

Tailwind는 속성 중심입니다.

overflow-hidden
rounded-3xl
border
p-6
text-base

두 방식은 보는 초점이 다릅니다.

CSS Modules
→ 이 요소가 어떤 역할인가?

Tailwind
→ 이 요소에 어떤 시각 속성이 붙어 있는가?

Button과 variant

포트폴리오에는 연락 버튼이 필요합니다.

Hero 섹션 아래에 버튼을 하나 추가한다고 생각해 봅시다.

<a href="mailto:[email protected]" className={styles.button}>
  연락하기
</a>

CSS Modules에서는 기본 버튼과 variant를 나눌 수 있습니다.

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 var(--space-6);
  border-radius: 999px;
  font-weight: 700;
  text-decoration: none;
}

.primaryButton {
  background: var(--color-brand);
  color: #ffffff;
}

.secondaryButton {
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
}

사용할 때는 클래스를 조합합니다.

<a
  href="mailto:[email protected]"
  className={`${styles.button} ${styles.primaryButton}`}
>
  연락하기
</a>

Figma로 보면 이 구조입니다.

Button Component
├─ base style: 높이, radius, padding, 정렬
└─ variant
   ├─ primary: 보라색 배경
   └─ secondary: 흰 배경 + border

Tailwind 방식에서는 variant를 클래스 조합으로 표현합니다.

<a
  href="mailto:[email protected]"
  className="inline-flex min-h-12 items-center justify-center rounded-full bg-violet-500 px-6 font-bold text-white no-underline"
>
  연락하기
</a>

버튼이 많아지면 매번 긴 클래스를 반복하기보다 Button 컴포넌트로 분리합니다.

Button 컴포넌트
- variant: primary | secondary
- size: small | medium | large

이때 개발자는 조건에 따라 클래스를 바꿉니다. 공식 학습 코스에서는 조건에 따라 클래스 이름을 쉽게 토글할 때 clsx 같은 도구를 사용할 수 있다고 소개합니다.1

디자이너는 여기서 중요한 질문을 할 수 있습니다.

Figma의 Button variant가 코드에서도 variant로 구현되어 있나요?
primary, secondary, disabled 상태 이름이 서로 같나요?

이 질문은 디자인 시스템과 코드 컴포넌트를 연결하는 질문입니다.

반응형 레이아웃: Auto Layout과 Constraints로 이해하기

반응형은 화면 크기에 따라 레이아웃이 바뀌는 규칙입니다.

디자이너에게는 이미 익숙한 개념입니다.

Figma에서 모바일, 태블릿, 데스크톱 프레임을 만들 때 이런 고민을 합니다.

카드가 2열로 있을 것인가, 1열로 쌓일 것인가?
이미지는 오른쪽에 둘 것인가, 위로 올릴 것인가?
섹션 padding은 유지할 것인가, 줄일 것인가?
텍스트 크기는 언제 줄일 것인가?

CSS에서도 같은 결정을 합니다.

지난 장에서 쓴 코드를 다시 보겠습니다.

.projectGrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

@media (max-width: 760px) {
  .projectGrid {
    grid-template-columns: 1fr;
  }
}

이 뜻은 간단합니다.

기본 상태
→ 프로젝트 카드를 2열로 보여 준다

화면 너비가 760px 이하일 때
→ 프로젝트 카드를 1열로 쌓는다

Figma로 비유하면 이렇습니다.

Desktop Frame
Project Grid: Horizontal, 2 columns

Mobile Frame
Project Grid: Vertical, 1 column

@media (max-width: 760px)는 일종의 분기점입니다. 보통 breakpoint라고 부릅니다.

breakpoint
→ 레이아웃 규칙이 바뀌는 화면 너비 기준점

Figma에서 데스크톱 프레임과 모바일 프레임을 따로 만들어 비교하듯, CSS에서는 breakpoint를 기준으로 규칙을 바꿉니다.

Hero 섹션도 마찬가지입니다.

.heroSection {
  display: grid;
  grid-template-columns: 1fr auto;
}

@media (max-width: 760px) {
  .heroSection {
    grid-template-columns: 1fr;
  }
}

해석하면 이렇게 됩니다.

데스크톱
소개 문구 | 프로필 이미지

모바일
소개 문구
프로필 이미지

Tailwind에서는 같은 규칙을 클래스 이름으로 표현합니다.

<section className="grid grid-cols-1 gap-10 rounded-[32px] bg-white p-8 md:grid-cols-[1fr_auto] md:p-14">
  ...
</section>

여기서 md:는 특정 화면 크기 이상에서 적용되는 조건입니다.

grid-cols-1
→ 기본은 1열

md:grid-cols-[1fr_auto]
→ md 이상에서는 텍스트와 이미지를 나란히 배치

p-8
→ 기본 padding

md:p-14
→ md 이상에서는 더 큰 padding

Figma의 Auto Layout과 Constraints로 다시 말하면 이렇습니다.

작은 프레임
→ 세로 오토레이아웃, padding 작게

큰 프레임
→ 가로 배치, padding 크게

반응형에서 중요한 것은 모든 화면을 완전히 새로 디자인하는 것이 아닙니다.

무엇은 유지하고, 무엇은 바뀌어야 하는지 규칙을 정하는 것입니다.

유지할 것
- 컴포넌트의 의미
- 콘텐츠 순서
- 브랜드 톤
- 기본 간격 체계

바뀔 것
- 열 개수
- 섹션 padding
- 이미지 위치
- 텍스트 크기

디자이너가 이 규칙을 Figma에서 명확히 보여 주면 개발자는 breakpoint를 정하기 쉬워집니다.

CSS Modules와 Tailwind, 무엇을 선택해야 할까

처음 배우는 입장에서는 이렇게 정리하면 됩니다.

기준CSS ModulesTailwind CSS
스타일 위치별도 .module.css 파일JSX의 className 안
읽는 방식역할 이름 중심시각 속성 중심
Figma 비유컴포넌트 내부 레이어 스타일속성 토큰 칩 조합
장점구조가 깔끔하고 충돌이 적음빠르게 조합하고 수정하기 쉬움
부담CSS 파일을 오가며 봐야 함클래스가 길어질 수 있음
잘 맞는 상황복잡한 컴포넌트 스타일, 전통 CSS 선호빠른 UI 제작, 토큰 기반 조합

중요한 것은 도구 이름이 아닙니다.

둘 다 결국 같은 질문에 답합니다.

이 색은 어디에서 왔는가?
이 간격은 어떤 기준인가?
이 컴포넌트의 상태는 몇 개인가?
모바일에서 언제 레이아웃이 바뀌는가?

디자이너가 Next.js 스타일링을 이해하면 화면을 더 잘 구현하기 위해 개발자가 어떤 결정을 해야 하는지 보입니다.

그리고 핸드오프 문서도 달라집니다.

예전 핸드오프가 이렇게 생겼다면,

카드 padding은 24px입니다.
버튼 색은 보라색입니다.
모바일에서는 한 줄로 쌓아 주세요.

이제는 이렇게 말할 수 있습니다.

카드 padding은 spacing scale의 6번 토큰을 사용합니다.
버튼 primary는 color.brand.primary를 사용합니다.
Project Grid는 desktop에서 2열, 760px 이하에서 1열로 전환됩니다.
Button은 primary, secondary, disabled variant가 필요합니다.

이 차이는 큽니다.

첫 번째는 화면 설명입니다. 두 번째는 구현 규칙입니다.

작은 실습: 포트폴리오 스타일 규칙 적어 보기

이번 장의 실습은 코드를 많이 쓰는 것보다, 스타일 규칙을 언어로 정리하는 데 초점을 둡니다.

아래 표를 자신의 포트폴리오 기준으로 채워 보세요.

Color
- background:
- surface:
- text:
- text muted:
- brand primary:
- border:

Spacing
- section padding desktop:
- section padding mobile:
- card padding:
- card gap:
- section gap:

Radius
- section radius:
- card radius:
- button radius:

Typography
- hero title desktop:
- hero title mobile:
- body text:
- eyebrow:

Responsive
- project grid desktop:
- project grid mobile:
- breakpoint:

예시는 이렇습니다.

Color
- background: #f7f3ee / --color-bg
- surface: #ffffff / --color-surface
- brand primary: #7a5cff / --color-brand

Spacing
- section padding desktop: 48px / --space-12
- section padding mobile: 32px / --space-8
- card gap: 24px / --space-6

Responsive
- project grid desktop: 2 columns
- project grid mobile: 1 column
- breakpoint: 760px

이 표는 단순한 메모가 아닙니다.

개발자와 대화할 수 있는 구현 명세의 시작입니다.

Figma에서 화면을 만들 때도 이 표를 옆에 두고 확인해 보세요.

이 28px 간격은 정말 필요한가?
아니면 spacing scale에 없는 예외인가?
이 보라색은 brand primary인가?
아니면 임시로 고른 색인가?
이 카드 radius는 기존 카드와 같은가?

디자인 시스템은 거창한 문서에서 시작하지 않습니다.

반복되는 결정을 이름 붙이고, 같은 이름으로 다시 쓰는 것에서 시작합니다.

오늘 배운 멘탈 모델

이번 장에서 가져갈 핵심은 네 가지입니다.

첫째, globals.css는 사이트 전체의 기본 스타일입니다.

globals.css
→ 전체 배경, 기본 글꼴, reset, 공통 토큰

둘째, CSS Modules는 특정 페이지나 컴포넌트에만 적용되는 로컬 스타일입니다.

page.module.css
→ page.tsx 전용 스타일

셋째, Tailwind는 미리 정해진 스타일 조각을 JSX 안에서 조합하는 방식입니다.

p-6 + rounded-3xl + bg-white + grid
→ padding, radius, background, layout 조합

넷째, 반응형은 Figma의 Auto Layout과 Constraints를 코드 규칙으로 옮기는 일입니다.

Desktop 2열
Mobile 1열
→ breakpoint에서 grid 규칙 변경

스타일링을 이해하면 개발자처럼 모든 CSS를 직접 짤 필요는 없습니다.

대신 이런 것을 볼 수 있습니다.

디자인 토큰이 코드에 반영되어 있는가?
컴포넌트 스타일이 전역으로 새고 있지 않은가?
모바일 규칙이 Figma와 같은가?
버튼 variant 이름이 디자인과 코드에서 같은가?

이것이 디자이너가 Next.js 스타일링을 배워야 하는 이유입니다.

디자이너가 개발자에게 이제 할 수 있는 질문

이번 장을 끝냈다면, 이제 개발자에게 더 구체적인 질문을 할 수 있습니다.

  • "이 프로젝트는 CSS Modules를 주로 쓰나요, Tailwind를 주로 쓰나요, 아니면 둘을 함께 쓰나요?"

  • "전역 스타일과 컴포넌트 전용 스타일의 기준은 어떻게 나누고 있나요?"

  • "Figma의 컬러 변수 이름과 코드의 토큰 이름이 매핑되어 있나요?"

  • "spacing scale은 어떻게 정의되어 있나요? 예를 들어 4, 8, 12, 16, 24, 32px 같은 규칙이 있나요?"

  • "예외적인 간격값을 써도 되는 기준이 있나요, 아니면 토큰 안에서만 선택하나요?"

  • "Tailwind를 쓴다면 기본 scale을 그대로 쓰나요, 우리 디자인 시스템에 맞게 커스터마이즈했나요?"

  • "ProjectCard의 padding, radius, border는 토큰으로 관리되고 있나요?"

  • "Button의 primary, secondary, disabled variant는 코드에서도 같은 이름으로 구현되어 있나요?"

  • "반응형 breakpoint는 몇 px 기준인가요? 모바일, 태블릿, 데스크톱 기준을 어디에서 확인할 수 있나요?"

  • "프로젝트 카드가 2열에서 1열로 바뀌는 지점은 Figma 시안과 일치하나요?"

  • "상태에 따라 클래스가 바뀌는 컴포넌트는 어떤 방식으로 관리하나요? clsx 같은 도구를 쓰나요?"

이 질문들은 스타일 취향을 묻는 질문이 아닙니다.

디자인 시스템이 코드 안에서 실제로 유지되는 방식을 확인하는 질문입니다.

다음 장에서는 포트폴리오의 콘텐츠를 코드 안에 직접 적는 방식에서 한 단계 나아갑니다. API가 무엇인지, 외부 데이터가 어떻게 화면으로 들어오는지 살펴보겠습니다.

참고

1App Router: CSS Styling | Next.js

책 목록

Figma에서 실제 URL까지: 디자이너를 위한 Next.js 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기