본문 바로가기
Frontend2026년 9월 15일10분 읽기

CSS 타이포그래피 2026 — text-box-trim·text-wrap:balance로 줄맞춤 끝내기

YS
김영삼
조회 202
CSS 타이포그래피 2026 — text-box-trim·text-wrap:balance로 줄맞춤 끝내기

CSS 타이포그래피의 오랜 골칫거리 두 가지가 최근 표준 기능으로 해결됐다. 하나는 텍스트 상자 위아래에 생기는 정체불명의 여백(하프 리딩, half-leading)이고, 다른 하나는 제목이 어색하게 줄바꿈되는 문제다. 전자는 text-box-trim, 후자는 text-wrap: balance가 다룬다.

둘 다 "디자이너가 준 시안과 구현이 미묘하게 다른" 상황을 없애는 기능이다. 디자인 도구는 대개 글자의 시각적 경계를 기준으로 간격을 잡는데, 브라우저는 글꼴의 행 상자(line box)를 기준으로 잡기 때문에 생기는 차이다.

나는 이 문제 때문에 매번 마이너스 마진을 썼다. 제목 아래 24px 간격을 맞추려고 margin-bottom: 20pxmargin-top: -4px 같은 걸 넣었다. 폰트를 바꾸면 그 숫자가 전부 틀어졌다. 근본 원인을 알고 나서야 이 값들이 왜 폰트마다 달라지는지 이해했다.

하프 리딩 — 여백은 어디서 왔나

행 높이(line-height)가 글꼴의 콘텐츠 영역보다 크면, 그 차이를 위아래로 절반씩 나눠 배분한다. 이것이 하프 리딩이다. line-height: 1.5인 24px 텍스트라면 행 상자는 36px이고, 글자가 차지하지 않는 여분이 위아래 각각 들어간다.

/* 문제 상황 */
h2 {
  font-size: 32px;
  line-height: 1.3;     /* 행 상자 41.6px */
  margin-bottom: 16px;  /* 시각적으로는 16px보다 커 보인다 */
}

/* 기존 우회법 — 폰트 바꾸면 다시 조정해야 한다 */
h2 { margin-bottom: 16px; margin-top: -6px; }

/* 2026년 방식 — 시각적 경계 기준으로 잘라낸다 */
h2 {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;   /* 위는 대문자 높이, 아래는 베이스라인 기준 */
  margin-bottom: 16px;             /* 이제 진짜 16px로 보인다 */
}
무엇을 기준으로 자를까 text-box-edge의 값은 위/아래 기준을 각각 지정한다. 라틴 문자 중심 디자인에서는 cap alphabetic(대문자 높이~베이스라인)이 일반적이다. 한글은 대문자 개념이 없어 글꼴에 따라 결과가 달라질 수 있으니, 실제 사용하는 한글 폰트로 눈으로 확인하는 과정이 필요하다.

줄바꿈 — balance와 pretty

제목이 두 줄로 넘어갈 때 마지막 줄에 단어 하나만 남는 경우가 있다. 보기 싫고, 읽기도 나쁘다. text-wrap이 이 문제를 다룬다.

동작권장 용도
wrap기본 — 한 줄씩 채워서 넘긴다일반 본문(기본값)
balance줄 길이를 고르게 분배제목, 짧은 문구(몇 줄 이내)
pretty고아 줄(마지막 줄 한 단어) 방지 중심긴 본문 단락
nowrap줄바꿈 안 함라벨, 짧은 UI 텍스트
/* 제목: 줄 길이를 고르게 */
h1, h2, h3, .card-title {
  text-wrap: balance;
}

/* 본문: 마지막 줄에 단어 하나만 남는 것 방지 */
p, li {
  text-wrap: pretty;
}

/* 주의: balance 는 계산 비용 때문에 브라우저가 줄 수를 제한한다.
   긴 본문 전체에 balance 를 거는 것은 의미가 없다. */

한글은 단어 단위 줄바꿈 규칙이 라틴과 달라서, word-break: keep-all과 함께 쓸 때 결과가 좋다. 어절 중간에서 끊기지 않게 하고, 그 위에 균형 잡힌 줄바꿈을 얹는 조합이다.

/* 한글 제목 조합 */
.title-ko {
  word-break: keep-all;     /* 어절 단위 유지 */
  text-wrap: balance;       /* 줄 길이 균형 */
  overflow-wrap: anywhere;  /* 아주 긴 단어(URL 등)는 예외적으로 끊기 */
}

폰트 폴백 시 레이아웃 흔들림 줄이기

웹폰트가 로드되기 전에는 대체 폰트로 표시되고, 로드된 뒤 교체되면서 레이아웃이 흔들린다(CLS). 두 폰트의 metric이 다르기 때문이다. size-adjust와 관련 디스크립터로 대체 폰트를 실제 폰트에 맞춰 보정할 수 있다.

/* 실제 웹폰트 */
@font-face {
  font-family: 'Pretendard';
  src: url('/fonts/Pretendard-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

/* 대체 폰트를 실제 폰트 metric 에 맞춰 보정 */
@font-face {
  font-family: 'Pretendard Fallback';
  src: local('Apple SD Gothic Neo'), local('Malgun Gothic');
  size-adjust: 100%;          /* 글자 크기 비율 보정 */
  ascent-override: 92%;       /* 위 여백 보정 */
  descent-override: 24%;      /* 아래 여백 보정 */
  line-gap-override: 0%;
}

body {
  font-family: 'Pretendard', 'Pretendard Fallback', sans-serif;
}

보정 값은 폰트마다 다르므로 실측이 필요하다. 개발자 도구에서 두 폰트로 같은 문단을 렌더링해 높이를 비교하며 맞춘다. 수고스럽지만 한 번 해 두면 폰트 교체 시 레이아웃 점프가 거의 사라진다.

읽기 좋은 본문을 위한 기본값

본문 타이포그래피 체크리스트
한 줄 길이 45~75자 — max-width: 65ch 같은 상대 단위가 편하다
본문 행간 1.6~1.9 — 한글은 라틴보다 조금 넉넉한 편이 읽기 좋다
제목 행간은 1.2~1.35 — 본문과 같은 값을 쓰면 뜬 느낌이 난다
문단 간격은 행간보다 커야 문단 경계가 보인다
대비 확인 — 본문 색이 배경 대비 4.5:1 이상인가
글자 크기는 16px 이상 — 모바일에서 특히 중요
숫자 정렬이 필요한 표에는 font-variant-numeric: tabular-nums
/* 표에서 숫자가 흔들리지 않게 */
.data-table td.num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 본문 기본 골격 */
.prose {
  max-width: 65ch;
  line-height: 1.8;
  font-size: 1rem;
  word-break: keep-all;
  text-wrap: pretty;
}
.prose h2 { line-height: 1.3; text-wrap: balance; text-box-trim: trim-both; }
.prose p + p { margin-top: 1.25em; }

점진적 적용 전략

새 기능은 지원 브라우저에서만 동작하면 되는 성격이라 적용이 쉽다. 지원하지 않는 브라우저에서는 기존과 똑같이 보일 뿐, 깨지지 않는다. 다만 text-box-trim처럼 여백 계산이 달라지는 속성은 마이너스 마진 같은 기존 우회 코드와 충돌할 수 있으니, 도입할 때 그 우회 코드를 함께 제거해야 한다.

/* 지원 여부에 따라 분기하고 싶다면 */
@supports (text-box-trim: trim-both) {
  h2 { text-box-trim: trim-both; text-box-edge: cap alphabetic; margin-top: 0; }
}
@supports not (text-box-trim: trim-both) {
  h2 { margin-top: -6px; }   /* 기존 우회 유지 */
}

자주 묻는 질문

text-box-trim은 무엇을 해결하나요?

행 높이 때문에 텍스트 위아래에 생기는 하프 리딩 여백을 제거해, 글자의 시각적 경계를 기준으로 간격을 잡을 수 있게 합니다. 디자인 시안의 간격 값과 구현 결과가 어긋나는 문제를 마이너스 마진 없이 해결합니다.

text-wrap의 balance와 pretty는 어떻게 다른가요?

balance는 여러 줄의 길이를 고르게 나눠 제목처럼 짧은 텍스트에 적합하고, pretty는 마지막 줄에 단어 하나만 남는 상황을 줄이는 데 초점이 있어 긴 본문에 적합합니다. balance는 계산 비용 때문에 적용 줄 수에 제한이 있습니다.

한글에서도 잘 동작하나요?

text-wrap은 word-break: keep-all과 함께 쓰면 어절 단위 유지와 줄 균형을 동시에 얻을 수 있어 효과적입니다. text-box-edge의 cap 기준은 대문자 개념에 기반하므로, 한글 폰트에서는 실제 렌더링을 눈으로 확인해 값을 조정하는 것이 좋습니다.

웹폰트 교체 시 레이아웃이 흔들리는 문제는 어떻게 줄이나요?

대체 폰트를 @font-face로 정의하고 size-adjust, ascent-override, descent-override로 실제 폰트의 metric에 맞춰 보정하세요. 값은 폰트마다 다르므로 같은 문단을 두 폰트로 렌더링해 높이를 비교하며 실측해야 합니다.

지원하지 않는 브라우저에서는 어떻게 되나요?

해당 속성이 무시되어 기존과 동일하게 표시되며 레이아웃이 깨지지는 않습니다. 다만 마이너스 마진 같은 기존 우회 코드가 남아 있으면 지원 브라우저에서 간격이 과하게 좁아질 수 있으니, @supports로 분기하거나 우회 코드를 함께 정리하세요.

본문 한 줄 길이는 어떻게 정하나요?

읽기 편한 범위는 대략 45~75자이며 CSS에서는 max-width를 ch 단위로 지정하면 폰트 크기 변화에 따라 함께 조정됩니다. 한글은 글자 폭이 넓어 같은 ch 값에서도 라틴보다 짧게 보이므로 실제 화면에서 확인해 조정하세요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록