line-clamp-1을 붙였는데 텍스트가 안 잘립니다. 콘솔에 에러도 없고, 개발자 도구로 열어보면 클래스도 얌전히 붙어 있습니다. 그런데 …은 끝내 안 나옵니다.
저는 이 상황에서 한참을 헤맸습니다. 원인은 바로 옆에 붙어 있던 flex 한 줄이었는데, 그걸 알고 나니 truncate와 line-clamp이 왜 다르게 동작하는지가 같이 풀렸습니다.
두 유틸이 어떻게 다른지, 그리고 말줄임이 소리 없이 안 먹을 때 어디부터 봐야 하는지 적어 봤습니다.
이름은 비슷한데 기반이 다릅니다
둘 다 넘치는 텍스트를 …로 줄여 주니 비슷해 보이지만, 실제로 생성되는 CSS를 나란히 놓으면 기반이 다릅니다.
/* truncate */
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* line-clamp-1 */
.line-clamp-1 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}truncate는 white-space: nowrap으로 줄바꿈을 막고, 넘치는 부분을 text-overflow: ellipsis로 자릅니다. 한 줄 전용이고 display는 건드리지 않습니다.
line-clamp은 여러 줄을 자를 수 있는 대신, display: -webkit-box라는 옛날 webkit 박스 모델 위에서만 동작합니다. N줄 말줄임 자체가 그 위에 얹혀 구현돼 있기 때문입니다.
한 줄(line-clamp-1)도 되긴 하지만, 이걸 쓰는 순간 그 엘리먼트의 display를 가져간다는 게 핵심입니다.
webkit 박스 모델(
-webkit-box)이란?
지금 쓰는display: flex가 표준이 되기 전, 2009년경 webkit이 먼저 구현한 1세대 플렉스 레이아웃입니다. 표준에서는 밀려났지만-webkit-line-clamp(여러 줄 말줄임)이 이 박스 모델 위에 얹혀 있어서, 지금도 N줄 말줄임을 쓰려면display: -webkit-box를 함께 선언해야 합니다. 표준은 아니지만 모든 주요 브라우저가 지원합니다.
truncate | line-clamp-N | |
|---|---|---|
| 줄 수 | 한 줄만 | N줄 (1줄 포함) |
| 핵심 속성 | white-space: nowrap + text-overflow | display: -webkit-box + -webkit-line-clamp |
display 점유 | 안 함 | 함 (-webkit-box) |
| 동작 기반 | 블록 안 인라인 텍스트 | webkit 박스 모델 |
한 줄이면 둘 다 됩니다. 하지만 여러 줄을 자르려면 line-clamp뿐이고, 그 대가로 display를 내줘야 합니다. 제가 헤맸던 지점이 바로 이 대목이었습니다.
안 잘리면 같은 엘리먼트의 display부터 봅니다
제 경우가 딱 이랬습니다. line-clamp과 flex를 같은 엘리먼트에 얹은 것입니다.
<span className="... flex items-center line-clamp-1">{label}</span>세로 중앙 정렬이 필요해서 flex items-center를 붙였고, 말줄임이 필요해서 line-clamp-1을 붙였습니다. 둘 다 흔한 유틸이라 의심조차 안 했는데, line-clamp-1은 조용히 무시되고 있었습니다.
flex는 display: flex를, line-clamp은 display: -webkit-box를 선언합니다. display는 값이 하나뿐이라, 최종 스타일시트에서 뒤에 오는 선언만 살아남습니다. -webkit-box가 밀려나면 line-clamp은 기댈 기반이 없어 조용히 무력화됩니다.
충돌이라기보다 같은 display를 다른 값으로 덮어쓴 것에 가깝습니다.
flowchart TD
A["한 엘리먼트에 line-clamp + flex"] --> B["line-clamp → display: -webkit-box"]
A --> C["flex → display: flex"]
B --> D["display 값은 하나뿐, 뒤 선언이 이김"]
C --> D
D --> E["-webkit-box 제거 → 말줄임 무력화"]
truncate로 바꿔도 flex 컨테이너에 직접 주면 마찬가지입니다. text-overflow: ellipsis는 블록 안에서 넘치는 인라인 텍스트를 자르는데, display: flex가 되면 자식이 flex item으로 배치되어 애초에 ellipsis 적용 대상에서 빠집니다.
그래서 말줄임이 안 먹을 때 저는 같은 엘리먼트에 display를 바꾸는 다른 유틸이 있는지부터 봅니다. flex, grid가 대표적입니다.
레이어를 나누면 풀립니다
원인이 “한 엘리먼트가 display를 두 용도로 못 쓴다”는 것이라면, 해결도 거기서 나옵니다. 바깥과 안쪽 두 레이어로 역할을 나누면 됩니다. 바깥은 레이아웃을 맡고, 안쪽 별도 span이 말줄임을 맡습니다.
<span className="... flex items-center min-w-0">
<span className="truncate" title={label}>
{label}
</span>
</span>- 바깥 span은
flex items-center로 세로 중앙 정렬을 맡습니다. - 안쪽 span은
truncate로 말줄임을,title로 hover 시 전체 텍스트를 맡습니다.
display를 두고 다투던 두 역할이 서로 다른 엘리먼트로 갈라지니, 경합할 일이 없어집니다.
그런데 여기서 한 가지를 더 봐야 하는데, flex item의 최소 너비입니다. flex item은 기본값이 min-width: auto라 콘텐츠의 min-content 아래로는 줄어들지 않아서, 긴 텍스트가 셀을 무시하고 밀고 나가 애초에 넘침(overflow) 자체가 안 생기기도 합니다.
푸는 방법은 둘입니다.
- 바깥에
min-w-0(min-width: 0)을 줘서 줄어들 수 있게 합니다. - 스펙상 flex item의
overflow가visible이 아니면 자동 최소 너비가 0이 됩니다(CSS Flexbox §4.5).truncate에는overflow: hidden이 들어 있으니, 안쪽 truncate span 자체가 이미 이 조건을 충족합니다.
즉 위 코드는 안쪽 truncate만으로도 동작하고, 바깥 min-w-0은 없어도 됩니다. 다만 구조가 조금만 복잡해져도 어디서 너비가 안 줄어드는지 추적하기 번거로워서, 저는 방어적으로 바깥에 min-w-0을 같이 두는 편입니다.