
【Frontend CSS – 파트 15】 Fluid Typography 완전 가이드 — clamp(), viewport 단위, 스케일러블한
요약
반응형 웹 디자인에서 글자 크기가 브레이크포인트마다 급격히 변하는 문제를 해결하기 위한 Fluid Typography 기법을 소개합니다. clamp() 함수와 viewport 단위를 활용하여 화면 크기에 따라 글자 크기가 매끄럽게 변화하도록 구현하는 방법을 다룹니다.
핵심 포인트
- 기존 미디어 쿼리 방식의 불연속적인 글자 크기 변화 문제 지적
- vw 단위를 사용한 연속적 신축의 장점과 상/하한선 부재라는 단점 설명
- clamp() 함수를 활용한 유연하고 접근성 있는 타이포그래피 구현법
- 사용자 경험(UX)을 해치지 않는 매끄러운 스케일링의 중요성
주의사항
이 기사는 AI의 지원을 받아 작성되었습니다.
이런 경험, 없으신가요?
케이스 1: 데스크톱에서 H1 48px
— 예쁩니다. 하지만 모바일에서는 화면의 절반 가까이를 차지합니다 — 디자이너로부터 즉시 연락이 옵니다. @media (max-width: 768px) { h1 { font-size: 24px; } }를 추가합니다. 태블릿 820px은? 노트북 1024px은? 테스트를 할 때마다 breakpoint가 늘어나고 CSS는 비대해지는데, 글자는 **점프(jump)**할 뿐 **매끄럽게 신축(smoothly scale)**하지 않습니다 — 마치 엘리베이터가 1층씩 끊어서 이동하는 것과 같습니다. -
케이스 2: vw로 font-size를 신축
— 우아하게 들립니다. 하지만 4K 모니터에서 열면 광고 배너급 글자 크기가 됩니다. 윈도우를 320px로 줄이면 논문의 각주만큼 작아집니다. 순수 vw에는 하한선도 상한선도 없습니다 — 신축은 하지만 멈출 줄을 모릅니다. -
케이스 3: clamp()를 넣어서 완료했다고 생각했다.
QA로부터 "200% 확대 시 H1이 커지지 않고 오히려 작아진다"라는 보고를 받습니다. 저도 이 버그의 trace를 찾는 데 반나절을 보낸 적이 있습니다. 줌(zoom) 시 preferred 내의 vw가 재계산되어 font-size가 역방향으로 움직입니다. fluid type은 현대적으로 들리지만, accessible(접근성)하다고 단정할 수는 없습니다. -
케이스 4: H1부터 caption까지 일관된 type scale을 원한다 — 각 레벨이 mobile과 desktop 사이에서 매끄럽게 신축되기를 원합니다. 각 레벨의 clamp()를 일일이 계산... 계산기(calculator)는 친구지만, 손가락은 피곤합니다.
요점: "글자가 깨지면 media query를 추가한다"는 responsive typography는 설계가 아니라 **불 끄기(firefighting)**입니다. 그리고 불을 꺼도 또 불이 납니다.
코드로 들어가기 전에, 아래 표로 이미지를 잡아보세요. body text가 viewport 320px에서 16px, 1200px에서 24px까지 연속적으로 — 점프 없이 — 신축하는 경우:
| Viewport width | Font-size (px) | 비고 |
|---|---|---|
| 320px | 16.0 | 시작점 (min) |
| ... | ... | ... |
viewport가 1px 줄었다고 해서 20px에서 16px로 "툭" 떨어지는 일은 없습니다. 읽기 경험이 계속 매끄러워집니다. 그것이 fluid typography가 지향하는 바입니다.
Fluid Typography(responsive typography, flexible type — 부르는 방식은 무엇이든 상관없음)는 breakpoint에서 갑자기 변하는 것이 아니라, viewport에 맞춰 글자 크기가 매끄럽게 신축되는 기법입니다.
media query는 breakpoint마다 글자 크기를 결정하게 합니다. breakpoint 사이에서 글자는 움직이지 않습니다.
/* 기존 방식: 3 breakpoint, 3 값 */
h1 {
font-size: 48px; /* desktop */
...
문제: 481px~767px 사이는 28px 그대로 유지됩니다. 480px이 되는 순간 — 툭 — 20px이 됩니다. 사용자 경험은 뚝뚝 끊기고, 코드는 장황해집니다.
vw(1vw = viewport 너비의 1%)를 사용하면 font-size가 연속적으로 신축됩니다:
h1 {
font-size: 5vw; /* viewport 너비의 5% */
}
단점: 상한선도 하한선도 없습니다. 4K(3840px) → 글자 192px, 읽을 수 없습니다. 320px → 16px, 근시가 아니라면 읽기 힘듭니다. 바닥과 천장이 필요합니다.
clamp(min, preferred, max)는 세 가지를 하나로 묶습니다: 하한선(min), 상한선(max), 그리고 그 사이의 신축 값(preferred — 대개 rem + vw). 한 줄로 여러 개의 media query를 대체할 수 있으며, vw의 폭주도 막을 수 있습니다.
구문의 상세 내용, media query와의 비교, 실전 패턴은 섹션 4에서 다룹니다.
세 개의 인자 — min~max 범위 안에 "곡선(curve)"이 있는 이미지:
clamp(minimum, preferred, maximum)
| 인자 (Argument) | 의미 | 예시 |
|---|---|---|
minimum | 최솟값 | 1rem, 16px, 1.5rem |
preferred | 가변값 (Flexible value) | 2.5vw, calc(1rem + 1vw), 5cqw |
maximum | 최댓값 | 3rem, 48px, 4rem |
clamp(MIN, VAL, MAX)는 max(MIN, min(VAL, MAX))와 동일합니다. 브라우저가 범위 내의 값을 선택합니다.
H1 예시:
h1 {
font-size: clamp(1.5rem, 5vw, 4rem);
}
- 작은 화면:
1.5rem미만으로 내려가지 않음 - 큰 화면:
4rem을 초과하지 않음 - 그 사이:
5vw에 따라 유동적으로 변화
동일한 H1을 구현하는 두 가지 방식:
/* 방법 1: 3개의 미디어 쿼리 (media query) */
h1 {
font-size: 2rem; /* 32px - mobile */
...
padding, gap, width 등 어디에나 사용할 수 있습니다. Typography (타이포그래피)와 Spacing (간격)이 동일한 "유동적인 리듬"으로 움직이게 됩니다:
.article {
font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem);
padding: clamp(1rem, 3vw, 3rem);
...
}
line-height (줄 높이)에 대하여: 많은 프로젝트에서는 유동적인 font-size만으로도 충분합니다. 단위가 없는 (unitless) 고정 line-height (예: 1.5)를 사용하는 것이 유동적인 line-height보다 유지보수가 쉽고 결과도 안정적입니다. font-size에 맞춰 line-height도 함께 바꾸고 싶다면 주의해야 합니다. Vertical rhythm (수직 리듬)이 깨지기 쉽기 때문입니다.
여기서부터는 약간의 수학이 필요합니다. 하지만 한 번 이해하고 나면, Type scale (타이포그래피 스케일) 전체를 감으로 만들 필요가 없어집니다.
다음과 같은 설계를 원한다고 가정해 봅시다:
- 화면 320px (mobile): font-size = 16px (1rem)
- 화면 1200px (desktop): font-size = 24px (1.5rem)
- 이 두 지점 사이에서 font-size가 선형적으로 (linearly) 변화
Viewport (뷰포트) → font-size의 두 지점을 앵커(anchor)로 설정합니다:
minFontSize(px) @minViewport(px)maxFontSize(px) @maxViewport(px)
slope = (maxFontSize - minFontSize) / (maxViewport - minViewport)
intercept = minFontSize - slope * minViewport
/* rem (1rem = 16px) 및 vw로 변환 */
...
예시: min=320px→16px, max=1200px→24px
slope = (24 - 16) / (1200 - 320) = 8/880 ≈ 0.00909
intercept = 16 - 0.00909 * 320 ≈ 13.09px = 0.818rem
preferred = 0.909vw + 0.818rem
...
| Viewport width | 0.909vw + 0.818rem | 결과 (px) |
|---|---|---|
| 320px | 0.909 * 3.2 + 13.09 | ~16.0 |
| 480px | 0.909 * 4.8 + 13.09 | ~17.5 |
| ... | 균등하게 증가하며 점프 없음 | 섹션 2의 표와 일치 |
무료 계산기(calculator)들이 이 작업을 대신 해줍니다:
- Fluid Typography Calculator
- Clamp Generator
- Utopia.fyi — Type scale과 Space scale 모두 지원
h1, h2 등 요소마다 제각각 clamp()를 작성하면 어떤 식이 어떤 레벨에 적용되었는지 잊어버리게 됩니다. **CSS custom properties (CSS 사용자 정의 속성)**를 사용하여 정리하세요. 한 곳만 수정하면 사이트 전체가 변경됩니다.
다음 토큰은 Utopia.fyi 스타일입니다: xs부터 4xl까지, viewport 320px → 1200px.
토큰 세트 (:root + fluid spacing)
:root {
--font-size-xs: clamp(
0.75rem,
...
semantic HTML에 토큰을 할당하세요 — 디자이너가 "H1은 4xl"이라고 말하면, 개발자는 px를 외울 필요가 없습니다:
h1 {
font-size: var(--font-size-4xl);
}
...
토큰이 완성되었다면 production(운영 환경)으로 가져갑니다: CSS 파일 1개 + Text 컴포넌트 — 프로젝트에 복사하면 바로 작동합니다.
:root {
--font-size-xs: clamp(0.75rem, 0.659vw + 0.614rem, 0.875rem);
--font-size-sm: clamp(0.875rem, 0.727vw + 0.727rem, 1rem);
...
import React from 'react';
import './typography.css';
type TextVariant = 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
...
윈도우 크기를 조절하거나 (또는 DevTools의 responsive 모드 사용) — breakpoint(중단점)용 props 없이도 글자가 유연하게 변합니다:
import React from 'react';
import { Text } from './Typography';
const App: React.FC = () => {
...
clamp()는 사용하기 편리한 반면, 잘못 사용하기 쉽습니다. 제가 자주 실수하는 부분은 다음과 같습니다:
/* ❌ 사용하지 마세요 */
h1 {
font-size: clamp(24px, 5vw, 48px);
...
사용자가 브라우저의 기본 font-size를 변경할 경우 (예: 16px → 20px), px를 사용한 clamp는 이를 따르지 않습니다. rem을 사용하세요:
/* ✅ rem을 사용하세요 */
h1 {
font-size: clamp(1.5rem, 5vw, 3rem);
...
200% 확대 시 글자가 커지지 않고 (오히려 작아지는 경우)? 놀라지 마세요 — 이는 fluid type의 전형적인 accessibility (접근성) 버그입니다:
원인:
대처법:
- min(최소값)과 max(최대값)는
rem을 사용하세요 — 예외는 없습니다. vw는 보조적인 역할로만 두고,preferred(선호값)는rem + vw를 우선시하세요.- 병합(merge)하기 전에 Chrome, Firefox, Safari에서 200% 확대를 테스트하세요 — 육안 확인만으로는 부족합니다.
/* ✅ 더 안전한 방법 */
h1 {
font-size: clamp(1.5rem, 1rem + 2vw, 3rem);
...
iOS Safari는 이 한 줄이 없으면 멋대로 font-size를 조절하는 경향이 있습니다:
html {
-webkit-text-size-adjust: 100%;
}
fluid type가 좋긴 하지만, 모든 곳에 유연한 크기 변화가 필요한 것은 아닙니다:
- 제목 → 사용함
- 본문 → 사용함 (변화 폭은 작게 설정하는 것으로 충분함)
- 캡션 / 소문자 → 고정된
rem고려 (이미 작기 때문에 더 줄어들면 읽기 어려움) - 코드 / 고정폭 글꼴 → 가독성을 위해 고정하는 경우가 많음
Figma와 1440px viewport에서는 fluid type이 아름답게 보입니다 — 하지만 WCAG(웹 콘텐츠 접근성 지침)는 Figma가 아니라, 200% 확대를 사용하는 사용자를 기준으로 합니다.
사용자는 콘텐츠나 기능을 잃지 않고 텍스트를 200%까지 확대할 수 있어야 합니다.
clamp()를 사용할 때는, 확대 시 font-size가 여전히 증가하는지 — max 값이 너무 낮아서 성장이 멈춰버리지는 않았는지 확인하십시오.
| 토큰 세트 | 사용 시점 | 특징 |
|---|---|---|
| 섹션 6 (Utopia 스타일) | 전체 design system (xs → 4xl + spacing) 적용 시 | 단계(level)가 많고, 데스크톱(desktop)에서 신축성이 뚜렷함 |
아래 (rem + vw) | 접근성(a11y) 감사, 200% 확대 시 실패(fail) | rem이 주가 되며, vw는 살짝 보조하는 역할 |
나의 워크플로우: 섹션 6의 세트로 배포(ship) → 확대 테스트 → 실패(fail)하면 rem + vw로 교체:
:root {
--font-size-base: clamp(1rem, 0.8rem + 0.4vw, 1.125rem);
--font-size-lg: clamp(1.125rem, 0.9rem + 0.6vw, 1.25rem);
...
- min/max는
rem으로 설정.vw에 핸들을 넘겨주지 마십시오. - 200%·400% 확대 — 글자가 커지는가?
- OS의 글꼴 크기(설정 → 크게) — 이를 따르는가?
- 스크린 리더(Screen reader) — 내용을 읽을 수 있는가, 잘리지 않는가?
- 대비(Contrast) ≥ 4.5:1 (WCAG AA) — 흐릿한 글자 색상은 fluid type으로 해결할 수 없습니다.
조금 더 — 출시 전날 밤 11시에 브라우저 지원(browser support)을 구글링하지 않도록.
| 기능 | Chrome | Firefox | Safari |
|---|---|---|---|
clamp() | ✅ 79+ | ✅ 75+ | ✅ 13.1+ |
| Viewport units | ✅ 26+ | ✅ 19+ | ✅ 6+ |
| Container units | ✅ 105+ | ✅ 110+ | ✅ 16+ |
Internet Explorer: IE 11은 clamp()를 지원하지 않습니다. 아직 IE 대응이 필요합니까? 고정 font-size fallback 또는 polyfill을 사용하십시오 — 행운을 빕니다.
clamp()와 viewport units는 현대적인 브라우저에서 문제없습니다. Container units (cqw, cqh, cqi)는 비교적 최신 기능이며, 컴포넌트(component) 단위의 타이포그래피에 유용합니다 (제13~14회에서 다루었습니다).
- 모든
font-size는rem사용 (px금지) -clamp()의 min/max도rem사용 - Chrome, Firefox, Safari에서 200%·400% 확대 확인
-webkit-text-size-adjust: 100%설정- CSS 변수(CSS variables)로 type scale 구축
line-height는 단위 없는 숫자(unitless) 사용 (예:1.5)- 간격(spacing: padding, margin, gap)도 연동하여 신축성 있게 조절
- 오래된 브라우저용 fallback (IE11이 필요한 경우)
- 대비(Contrast) WCAG AA 확인
- merge 전 200% 확대 통과 — 특히 섹션 6의 토큰(token)
컴포넌트 기반의 UI에서는 viewport만이 척도가 아닙니다. 타이포그래피는 화면 전체가 아니라 카드의 너비에 맞춰지길 원할 것입니다. 그럴 때는 cqw, cqh, cqi 등이 vw보다 더 합리적입니다.
제13~14회를 아직 읽지 않으셨다면, 먼저 container-type을 가볍게 훑어보십시오 — 이곳은 '확장' 단계이지 총정리 섹션이 아닙니다.
카드 제목이 viewport가 아닌 카드 크기에 맞춰 신축적으로 변함:
.card-container {
container-type: inline-size;
}
...
sidebar, main, grid — 동일한 컴포넌트라도 실제 공간에 맞춘 글자 크기. "화면이 큰가 작은가"가 아니라 "지금 얼마만큼의 공간이 있는가"가 기준입니다. 자세한 내용은 제13~14회를 참고하십시오. 제16회에서 responsive 패턴을 정리하겠습니다.
fluid typography는 단순히 흩어진 몇 줄의 CSS가 아니라, **디자인 토큰(design token)**의 일부여야 합니다:
type scale을 토큰화하면:
- 변수를 몇 개 수정하는 것만으로 시스템 전체가 바뀝니다.
- 디자이너와 개발자(dev)가
--font-size-2xl과 같은 토큰으로 소통할 수 있습니다. "태블릿에서 36px로 할지 32px로 할지"에 대한 논쟁이 줄어듭니다. - 유지보수와 확장이 쉬워집니다.
올바른 질문은 "H1은 몇 px인가?"가 아니라, "어떤 규칙을 통해 H1이 모든 뷰포트(viewport)에 적응하고 — 동시에 확대(zoom)에도 견딜 수 있는가?"여야 합니다. clamp()는 도구일 뿐입니다. min/max 설계, 토큰(token)으로의 집약, 배포(ship) 전 확대 테스트 — 구문(syntax)보다 이 세 가지가 더 중요합니다.
몇몇 프로젝트에서 사용해 본 결과, 가장 큰 이점은 CSS 코드 줄 수의 감소가 아니라 브레이크포인트(breakpoint)마다 발생하는 문제들을 일일이 해결(firefighting)할 필요가 없어진다는 점이라고 느꼈습니다. 타이포그래피가 디자인 시스템(design system)의 일부가 되면, "레이아웃이 깨지면 미디어 쿼리(media query)를 추가한다"는 식의 반응형 타이포그래피(responsive typography) 방식에서 졸업할 수 있습니다.
📚 참고 자료:
- MDN: clamp()
- Smashing Magazine: Modern Fluid Typography
- Smashing Magazine: Accessibility Concerns With Fluid Type
- CSS-Tricks: clamp()
- Utopia.fyi – Fluid type & space scale
- WCAG 1.4.4 Resize Text
【Frontend CSS – 파트 16】 모던 반응형(Responsive) 패턴 — 모든 화면 크기에 대응하는 레이아웃 설계
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기