어색하게 끊기는 한국어 줄바꿈 다듬는 법

어색하게 끊기는 한국어 줄바꿈 다듬는 법

공들여 쓴 글에 제목을 달고 모바일에서 열어봤는데, 마지막 줄에 짧은 단어 하나만 덩그러니 남아 있는 걸 보신 적 있으신가요? 😅 글꼴도 크기도 잘 골랐는데 제목이 어딘가 엉성해 보이는데요. 한국어라면 여기에 더해 “브라우저”가 “브라” / “우저”로 뚝 잘리는 일까지 생깁니다.

이런 문제는 글꼴이나 여백을 아무리 만져도 해결되지 않습니다. 줄을 어디서 나눌지는 브라우저의 줄바꿈 알고리즘이 정하는 영역이기 때문인데요. 우리가 개입하려면 그 알고리즘에 직접 말을 걸어야 합니다.

이번 포스팅에서는 word-break 속성과 text-wrap 속성으로 한국어 제목과 본문의 줄바꿈을 다듬는 방법을 알아보겠습니다.

text-wrap은 두 속성을 묶은 단축 속성입니다

text-wrap을 제대로 쓰려면 이 속성이 단축 속성(shorthand)이라는 걸 알고 시작하는 게 좋은데요. 성격이 전혀 다른 두 개의 개별 속성(longhand)을 하나로 묶고 있기 때문입니다.

  • text-wrap-mode: 자동 줄바꿈을 할지 말지 정하며 wrapnowrap 값을 가집니다.
  • text-wrap-style: 줄을 어떻게 나눌지 정하며 auto, balance, pretty, stable 값을 가집니다.

그래서 text-wrap에 어떤 값을 넣느냐에 따라 실제로 건드리는 속성이 달라집니다.

.a {
  text-wrap: nowrap; /* text-wrap-mode를 지정 */
}

.b {
  text-wrap: balance; /* text-wrap-style을 지정 */
}

두 줄이 같은 속성 이름을 쓰고 있지만 하는 일은 전혀 다른 셈이죠.

여기서 text-wrap: nowrap은 사실 새로운 기능이 아닙니다. 예전부터 쓰던 CSS의 white-space 속성 사용법white-space: nowrap과 똑같은 자리를 가리키거든요. white-space 역시 white-space-collapsetext-wrap-mode를 묶는 단축 속성으로 재정의되면서, 두 단축 속성이 text-wrap-mode를 나눠 갖게 되었기 때문입니다.

정말로 새로 생긴 쪽은 text-wrap-style입니다. balance, pretty, stable은 줄바꿈을 할지 말지가 아니라 이미 나뉠 줄을 어떻게 배치할지 고르는 값들이고, 이 글에서 다룰 내용도 여기에 집중되어 있습니다.

word-break: keep-all로 어절부터 지키기

한국어에서는 순서상 text-wrap보다 먼저 손봐야 할 게 있습니다. 부모 요소 밖으로 삐져나오는 긴 영단어 처리법에서 살펴봤듯이, 웹에서 한국어는 기본적으로 단어 중간에서도 줄바꿈이 일어나기 때문인데요. 영어는 공백이나 하이픈이 없으면 단어를 쪼개지 않는 반면, 한국어는 글자 단위로 아무 데서나 끊깁니다.

이걸 막아주는 게 word-break 속성의 keep-all 값입니다.

<p class="normal">브라우저 호환성을 고려한 스타일시트 작성법</p>
<p class="label">↑ 기본값</p>
<p class="keep">브라우저 호환성을 고려한 스타일시트 작성법</p>
<p class="label">↑ word-break: keep-all</p>
body {
  font-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
}
.normal,
.keep {
  width: 10rem;
  margin: 0 auto;
  font-size: 1rem;
  line-height: 1.7;
  border: 1px dashed #ccc;
}
.keep {
  word-break: keep-all;
}
.label {
  width: 10rem;
  margin: 0.25rem auto 1.5rem;
  font-size: 0.8rem;
  color: #888;
}

기본값 쪽은 “고려한”이 “고려” / “한”으로 갈라져 어절 한가운데가 잘립니다. keep-all을 준 쪽은 어절이 통째로 보존되어 자연스럽게 읽힙니다.

keep-all은 이름만 보면 “줄바꿈을 아예 막는다”처럼 읽히지만 그렇지 않습니다. 정확히는 CJK 문자 사이에서 줄을 끊지 않는다는 뜻인데요. 한국어는 어절 사이에 공백을 쓰기 때문에 keep-all을 걸어도 공백에서는 여전히 줄이 바뀝니다. 공백이 없는 중국어나 일본어에 keep-all을 걸면 정말로 줄바꿈이 사라져 텍스트가 넘쳐버리죠. 같은 값인데 유독 한국어에서 잘 통하는 건 우리말이 띄어쓰기를 하기 때문입니다.

그래도 조심할 상황이 하나 있는데요. 좁은 화면에서 유난히 긴 어절이나 URL이 나오면 끊을 자리가 없어 부모 요소 밖으로 삐져나옵니다. 그래서 보통 overflow-wrap을 안전장치로 함께 걸어둡니다.

body {
  word-break: keep-all;
  overflow-wrap: break-word; /* 끊을 곳이 정 없으면 그때만 쪼갠다 */
}

text-wrap: balance로 제목 줄 길이 맞추기

어절을 지켰으면 이제 줄 길이를 맞출 차례입니다. 브라우저는 기본적으로 “한 줄을 꽉 채우고 남는 걸 다음 줄로 넘기는” 방식으로 줄을 나누는데요. 그러다 보니 마지막 줄에 단어 하나만 남는 어정쩡한 모양이 자주 나옵니다.

text-wrap: balance를 지정하면 브라우저가 여러 조합을 따져서 줄 길이 편차가 가장 작은 배치를 골라줍니다. 아래 예제에서 세 제목을 차례로 비교해보세요.

<h2 class="plain">프론트엔드 개발자를 위한 브라우저 호환성 완벽 정리 가이드</h2>
<p class="label">↑ 기본값</p>
<h2 class="keep">프론트엔드 개발자를 위한 브라우저 호환성 완벽 정리 가이드</h2>
<p class="label">↑ keep-all만</p>
<h2 class="both">프론트엔드 개발자를 위한 브라우저 호환성 완벽 정리 가이드</h2>
<p class="label">↑ keep-all + balance</p>
body {
  font-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
}
h2 {
  max-width: 16rem;
  margin: 0 auto;
  font-size: 1.3rem;
  line-height: 1.45;
  text-align: center;
  border: 1px dashed #ccc;
}
.keep {
  word-break: keep-all;
}
.both {
  word-break: keep-all;
  text-wrap: balance;
}
.label {
  max-width: 16rem;
  margin: 0.25rem auto 1.5rem;
  text-align: center;
  font-size: 0.8rem;
  color: #888;
}

세 단계가 각각 무엇을 고쳤는지 보이시나요? 기본값은 “브라우저”가 “브라” / “우저”로 갈라집니다. keep-all만 주면 어절은 지켜지지만 마지막 줄에 “가이드”만 외롭게 남고요. 둘을 함께 주면 세 줄이 고르게 나뉩니다.

여기서 짚고 갈 게 하나 있는데요. balance만 단독으로 쓰면 한국어에서는 기대한 효과가 나오지 않습니다. balance는 줄 길이를 맞추려고 끊을 지점을 고르는데, keep-all이 없으면 어절 한가운데도 후보로 삼기 때문입니다. 실제로 위 예제에서 .bothword-break 선언만 지워보시면 결과가 맨 위 기본값과 똑같아집니다. 어절을 마음대로 쪼갤 수 있으면 두 줄을 꽉 채운 배치가 이미 충분히 고르니, balance가 굳이 다른 조합을 찾을 이유가 없어지는 거죠. 더 곤란한 경우도 있는데요. 너비에 따라서는 기본값일 때 멀쩡하던 어절을 balance가 새로 갈라놓기도 합니다.

두 속성의 역할이 다르다고 이해하시면 쉽습니다. keep-all끊을 수 있는 지점을 공백으로 한정하고, balance그중에서 가장 고른 조합을 고르는 거죠. 그래서 한국어 사이트라면 이 둘을 세트로 쓰셔야 합니다.

다만 balance에는 제약이 하나 있는데요. 여러 배치를 계산해봐야 해서 비용이 큰 작업이라, 브라우저가 처리하는 줄 수에 제한을 둡니다. Chromium 계열은 6줄 이하, Firefox는 10줄 이하일 때만 적용되고 그보다 길어지면 조용히 무시됩니다.

그래서 balance는 본문이 아니라 제목, 카드 타이틀, 이미지 캡션, 인용구처럼 짧은 텍스트에 쓰는 값입니다. 본문 문단에 걸어두고 “왜 아무 변화가 없지?”라며 헤매는 경우가 대부분 여기서 나옵니다.

h1,
h2,
h3,
figcaption,
blockquote {
  word-break: keep-all;
  text-wrap: balance;
}

text-wrap: pretty로 본문 마지막 줄 챙기기

그럼 본문처럼 긴 텍스트는 방법이 없을까요? 이때 쓰는 값이 pretty입니다.

pretty는 줄 길이를 전부 맞추는 대신, 마지막 줄에 단어 하나만 남는 상황을 피하는 데 집중합니다. 활자 매체에서 고아 단어(orphan)라고 부르는 그 문제인데요. balance보다 계산 범위가 좁아서 줄 수 제한 없이 본문에도 쓸 수 있습니다.

<p class="normal">웹 타이포그래피에서 문단의 마지막 줄이 지나치게 짧으면 시선이 어색하게 끊기기 때문에 편집 디자이너들은 오래전부터 이런 줄을 피하려고 노력해 왔습니다</p>
<p class="label">↑ 기본값</p>
<p class="pretty">웹 타이포그래피에서 문단의 마지막 줄이 지나치게 짧으면 시선이 어색하게 끊기기 때문에 편집 디자이너들은 오래전부터 이런 줄을 피하려고 노력해 왔습니다</p>
<p class="label">↑ text-wrap: pretty</p>
body {
  font-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
}
.normal,
.pretty {
  max-width: 21rem;
  margin: 0 auto;
  font-size: 1rem;
  line-height: 1.7;
  word-break: keep-all;
  border: 1px dashed #ccc;
}
.pretty {
  text-wrap: pretty;
}
.label {
  max-width: 21rem;
  margin: 0.25rem auto 1.5rem;
  font-size: 0.8rem;
  color: #888;
}

기본값은 마지막 줄이 “왔습니다” 하나로 끝나는 반면, pretty 쪽은 윗줄에서 한 어절을 끌어내려 “노력해 왔습니다”가 됩니다.

솔직히 말씀드리면 prettybalance만큼 극적으로 티가 나지 않습니다. 줄 전체를 재배치하는 게 아니라 마지막 줄만 손보기 때문인데요. 문단 너비에 따라 아예 아무 변화가 없을 때도 있습니다. 위 예제에서 max-width 값을 조금씩 바꿔보시면 효과가 나타났다 사라졌다 하는 걸 확인하실 수 있습니다.

대신 손해 볼 일이 없는 속성이라 본문 전체에 깔아두고 잊어버려도 됩니다.

p,
li {
  word-break: keep-all;
  overflow-wrap: break-word;
  text-wrap: pretty;
}

text-wrap: stable로 입력 중 출렁임 막기

마지막 값인 stable은 쓰임새가 조금 특이합니다. contenteditable 영역처럼 사용자가 글자를 입력하는 중에 앞 줄들이 다시 배치되는 걸 막아주는데요.

prettybalance는 텍스트 전체를 보고 줄을 나누기 때문에, 글자를 하나 칠 때마다 이미 써둔 윗줄까지 출렁일 수 있습니다. stable은 이미 확정된 줄은 그대로 두고 커서가 있는 줄부터 다시 계산합니다.

<p class="label">아래 두 상자를 클릭해서 글자를 길게 입력해보세요</p>
<div class="editor pretty" contenteditable>여기에 한국어 문장을 계속 이어서 입력해보면 윗줄까지 다시 배치되는 모습을 볼 수 있습니다</div>
<p class="label">↑ text-wrap: pretty</p>
<div class="editor stable" contenteditable>여기에 한국어 문장을 계속 이어서 입력해보면 윗줄은 그대로 유지되는 모습을 볼 수 있습니다</div>
<p class="label">↑ text-wrap: stable</p>
body {
  font-family: system-ui, -apple-system, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
}
.editor {
  max-width: 18rem;
  margin: 0 auto;
  padding: 0.5rem;
  font-size: 1rem;
  line-height: 1.7;
  word-break: keep-all;
  border: 1px solid #ccc;
}
.pretty {
  text-wrap: pretty;
}
.stable {
  text-wrap: stable;
}
.label {
  max-width: 18rem;
  margin: 0.25rem auto 1.5rem;
  font-size: 0.8rem;
  color: #888;
}

에디터나 댓글 입력창처럼 사용자가 직접 타이핑하는 UI를 만든다면 기억해둘 만한 값입니다.

문장부호 줄바꿈을 다루는 line-break

word-break와 헷갈리기 쉬운 속성으로 line-break가 있는데요. 문장부호나 작은 글자 주변에서 줄바꿈을 얼마나 엄격하게 처리할지를 strict, normal, loose, anywhere 값으로 지정합니다.

다만 line-break가 정의하는 규칙은 대부분 일본어의 작은 가나와 문장부호를 겨냥하고 있는데요. 한국어에서는 strictnormal의 차이를 체감하기 어려운 경우가 많아서, 한국어 사이트라면 keep-all을 챙기는 편이 훨씬 효과가 큽니다.

한편 word-break에는 auto-phrase라는 값도 있는데요. 언어별 분석으로 구(phrase) 중간에서 끊기지 않게 해주는 값이지만, 아직 브라우저 지원이 제한적이고 일본어 위주로 구현되어 있어 지금 상용 사이트에 쓰기에는 이릅니다.

브라우저 지원

text-wrap 속성은 2024년 3월에 Baseline에 진입해서 주요 브라우저에서 두루 쓸 수 있습니다. word-break는 2015년부터 지원된 오래된 속성이라 keep-all은 마음 놓고 쓰셔도 됩니다.

다만 balance의 줄 수 제한이 브라우저마다 다르고, pretty의 구체적인 동작도 구현에 따라 차이가 있는데요. 그래도 이 값들은 지원하지 않는 브라우저에서 그냥 무시될 뿐 레이아웃이 깨지지 않기 때문에 점진적 향상(progressive enhancement)으로 접근하기 좋습니다. 없으면 예전처럼 보이고 있으면 조금 더 예쁘게 보이는 식이죠.

마치며

지금까지 한국어 줄바꿈을 다듬는 CSS 속성을 살펴봤는데요. 정리하면 word-break: keep-all로 어절을 지키는 게 먼저고, 그다음 제목에는 text-wrap: balance로 줄 길이를 맞추고 본문에는 text-wrap: pretty로 마지막 줄을 챙기면 됩니다. 여기에 긴 URL을 대비한 overflow-wrap: break-word까지 더하면 웬만한 상황은 커버됩니다.

특히 balancekeep-all 없이 단독으로 쓰면 한국어에서는 오히려 어절이 갈라질 수 있다는 점을 기억해두시면 좋겠습니다. 영어권 자료만 보고 text-wrap: balance 한 줄만 복사해 오면 놓치기 쉬운 부분이거든요.

운영 중인 사이트가 있다면 제목 선택자에 두 줄만 얹어보시고 모바일에서 확인해보세요. 두 줄 추가했을 뿐인데도 완성도가 꽤 달라졌다고 느끼실 겁니다. 텍스트 스타일링의 기본기가 궁금하시다면 텍스트 스타일링을 위한 10가지 CSS 속성을, 화면 크기에 따른 글꼴 조절이 궁금하시다면 CSS로 반응형 글꼴 스타일하기를 이어서 보시면 좋겠습니다.

더 자세한 내용은 MDN의 text-wrap 문서를 참고하세요.

This work is licensed under CC BY 4.0CCBY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord