본문으로 건너뛰기

© 2026 Molayo

CSS-T헤드라인2026. 05. 20. 01:36

고정 높이 카드: 보기보다 더 취약합니다

요약

디자인 목업에서 완벽해 보이는 고정 높이 카드 레이아웃이 실제 콘텐츠 변화(번역, 글꼴 크기 변경 등)에 따라 어떻게 깨지는지 분석합니다. 고정 높이 설정이 콘텐츠의 유연성을 방해하여 텍스트가 넘치거나 레이아웃이 무너지는 취약성을 설명합니다.

핵심 포인트

  • 고정 높이(Fixed height) 설정은 콘텐츠의 양이나 언어적 특성에 대응하지 못하는 취약성을 가짐
  • 다국어 번역(프랑스어, 독일어 등) 시 단어 길이가 길어지면 레이아웃 붕괴가 가속화됨
  • 사용자의 브라우저 글꼴 크기 확대 설정은 고정 높이 컨테이너 내에서 요소 간 충돌을 유발함
  • overflow: hidden은 문제를 해결하는 것이 아니라 단순히 시각적으로 숨길 뿐임
  • 유연한 레이아웃을 위해서는 고정 높이 대신 콘텐츠에 따라 크기가 변하는 구조가 필요함

고정 높이 카드(Fixed-height cards)는 종종 안전한 선택처럼 느껴집니다. 디자이너가 모든 카드가 그리드(grid) 내에서 완벽하게 정렬된 목업(mockup)을 전달합니다. 제목은 짧고, 발췌문은 깔끔하게 들어가며, 페이지 전체에 걸쳐 레이아웃이 안정적으로 보입니다. 그래서 당신은 명시된 대로 디자인을 정확히 구현하여 배포합니다.

콘텐츠가 바뀌기 전까지는 모든 것이 잘 작동합니다. 에디터가 문구를 업데이트하고, 번역 과정에서 더 긴 단어가 추가되며, 일부 사용자들은 가독성을 높이기 위해(특히 저시력자나 디지털 눈 피로를 느끼는 사용자들) 기본 글꼴 크기를 키우기도 합니다.

저는 블로그의 "최근 기사(Recent Articles)" 섹션을 구축하면서 이 문제에 직면했습니다. 디자인은 상대적으로 짧은 영어 제목을 가정했기에, 모든 것이 고정 높이(fixed height) 안에 편안하게 들어갔습니다.

처음 보았을 때 레이아웃은 견고해 보였습니다:

하지만 콘텐츠가 바뀌자 균열이 나타나기 시작했습니다:

콘텐츠를 프랑스어로 번역하자 상황은 더 악화되었습니다:

독일어 번역은 레이아웃을 더욱 한계로 몰아붙였습니다:

한때 안정적인 컴포넌트(component)처럼 보였던 것이, 콘텐츠가 항상 고정 높이 안에 머물 것이라는 취약한 가정에 의존하고 있었다는 사실이 드러났습니다.

레이아웃 데모는 다음과 같습니다:

고정 높이 레이아웃은 취약해 보입니다

디자인 사양(design specifications)에서 픽셀(pixel) 치수는 정확했습니다. 또한 카드가 동일한 수직 리듬(vertical rhythm)과 동일한 크기를 가질 때 더 깔끔하게 정렬된다는 것을 알고 있으며, 이는 저와 디자이너가 신뢰했던 일종의 질서감을 우리 마음속에 만들어냅니다.

그래서 저는 다음과 같이 설정했습니다:

.card__title {
margin: 0 0 8px;
font-size: 18px;
...

하지만 놀랍게도, 글꼴 설정이 바뀌자마자 동작이 변했습니다. 브라우저의 기본 텍스트 크기를 키워보았더니 카드 내부에서 압박이 발생하는 것을 확인했습니다. 텍스트 블록은 커졌지만 컨테이너(container)는 그대로 유지되었고, 요소들이 동일한 공간을 차지하기 위해 경쟁하기 시작했습니다.

일반적으로 블록 요소 (block element)는 콘텐츠와 함께 단순히 커집니다. 하지만 제가 그 높이를 설정하는 순간, 그 관계를 깨뜨려 버렸습니다. 브라우저는 이를 문제로 취급하지 않습니다. 그저 콘텐츠를 넘치게(overflow) 두거나 잘라내는(clipping) 방식으로, 자신이 할 수 있는 유일한 방법을 통해 충돌을 해결할 뿐입니다.

원래 레이아웃 버전에서는 overflow: hidden을 사용하여 이러한 문제들을 단순히 무식하게 숨겼습니다.

문제를 가시화하기 위해, 우리는 안전망을 제거할 수 있습니다:

.card__title {
  display: -webkit-box;
  font-size: 18px;
  ...
}

overflow: hidden이 없으면, 실패는 더 이상 미묘하지 않습니다. 콘텐츠는 잘리는 것을 멈추고, 마치 찢어진 봉투에서 쏟아져 나오는 식료품처럼 밖으로 흘러넘치기 시작합니다. 일부 발췌문은 태그 바로 위에 놓이게 되고, 카드 내부의 압력을 숨기는 것을 멈추자 모든 것이 무너졌습니다.

불행히도, 브라우저는 요소들이 충돌하게 두는 것 외에는 이러한 상충하는 지시사항들을 조정할 방법이 없습니다.

고정 높이 제거하기

이 레이아웃을 유지하던 제약 조건들을 제거하면 진짜 문제가 어디에 있는지 드러납니다. 고정 높이 (fixed heights), 절대 위치 지정 (absolute positioning), 그리고 그리드 정렬 (grid alignment)은 모두 동일한 것을 제어하려고 시도하고 있었습니다.

절대 위치 지정된 액션 (Absolutely Positioned Actions): 흐름에서 제거됨

지금까지는 고정 높이가 주요 범인처럼 보였습니다. 하지만 그것이 단독으로 행동하는 것은 아닙니다. 카드 하단의 액션 (actions) 요소들은 절대 위치 지정 (absolutely positioned)되어 있었습니다:

.card__actions {
  position: absolute;
  inset: 0 14px 14px;
  ...
}

이것은 깔끔한 해결책처럼 느껴집니다. 콘텐츠의 길이에 상관없이 액션들이 카드의 하단에 고정되어 있기 때문입니다.

전형적인 블록 레이아웃 (block layout)에서 컨테이너 (container)의 높이는 인플로우 (in-flow) 자식 요소들의 결합된 기여도에 의해 결정됩니다.

여러분은 절대 위치 지정된 요소들이 어떻게 동작하는지 이미 보셨을 것입니다. 브라우저는 이 요소들이 더 이상 부모의 고유 높이 (intrinsic height)에 기여하지 않음에도 불구하고 여전히 렌더링합니다. 시각적으로 액션은 카드에 속해 있지만, 구조적으로 레이아웃은 이를 무시합니다.

이를 보완하기 위해, 우리는 수동으로 공간을 확보했습니다:

.card__body {
  padding-block-end: 14px;
}

이 패딩(padding)은 사실상 추측에 불과합니다. 폰트 크기가 커지거나, 버튼이 줄바꿈되거나, 번역으로 인해 텍스트가 길어지는 순간, 이 추측은 더 이상 신뢰할 수 없게 됩니다.

액션(actions) 요소들이 얼마나 많은 공간을 필요로 할지 예측하려고 애쓰는 대신, 브라우저가 이를 계산하도록 맡길 수 있습니다.

다음은 절대 위치 지정 (absolute positioning)을 사용하지 않은 동일한 레이아웃입니다:

변경 사항은 작지만, 동작의 변화는 상당히 눈에 띕니다. 고정 높이가 여전히 적용되어 있음에도 불구하고, 레이아웃이 스스로와 충돌하지 않기 때문에 내부적인 긴장감이 줄어듭니다.

이것이 첫 번째 구조적 개선입니다. 카드는 여전히 외적 높이 제약 (extrinsic height constraint)을 가지고 있으므로, 레이아웃이 완전히 유연한 상태는 아닙니다.

통제의 환상

고정 높이가 천장처럼 작동한다면, 라인 클램핑 (line clamping)은 음소거 버튼처럼 작동합니다. 원래의 컴포넌트에서 저는 제목과 발췌문 (excerpt)을 클램핑했습니다:

.card__title {
  display: -webkit-box;
  overflow: hidden;
  ...
}

클램핑은 텍스트의 이탈을 제한하고 카드를 시각적으로 정렬된 상태로 유지해주기 때문에, 당시에는 안심이 되는 것처럼 느껴집니다. 하지만 실제로 보면, 이는 관계를 뒤집어 놓습니다.

이를 더 명확하게 확인하기 위해, 다른 모든 것은 그대로 둔 채 클램핑만 제거해 보겠습니다. 이 버전은 .card__title.card__excerpt에서 모든 클램핑을 제거했다는 점을 제외하면 이전 데모와 동일하지만, 어떤 일이 일어나는지 명확히 볼 수 있도록 overflow는 남겨두었습니다.

클램핑이 없으면 컴포넌트 내부의 긴장감이 명확해집니다. 독일어 카드가 어떻게 더 길어지는지, 그리고 발췌문이 어떻게 자연스럽게 줄바꿈되는지 볼 수 있습니다. 이것이 우리에게 정말로 보여주는 것은, 안정적인 레이아웃은 overflow: hidden에 의존해서는 안 된다는 점입니다. 만약 콘텐츠를 억제함으로써만 작동하는 레이아웃이라면, 그것은 아마도 취약할 것입니다.

지금까지 우리가 목격한 거의 모든 실패는 단 하나의 결정으로 거슬러 올라갑니다:

.card {
  height: 375px;
}

이 한 줄은 여러분에게 무해해 보일지 모르지만, 브라우저의 기본 크기 조정 동작 (default sizing behavior)을 무시합니다.

어느 시점에 이르면, 가장 단순한 질문이 피할 수 없게 됩니다. 만약 우리가 그냥... 멈춘다면 어떻게 될까요? 높이를 완전히 제거하고 브라우저가 알아서 하게 내버려 둔다면 말이죠?

나머지 레이아웃은 그대로 유지하면서 고정 높이 (fixed height)만 제거해 보겠습니다. 동작 방식을 비교해야 하므로 클램핑 (Clamping)은 그대로 유지합니다.

카드 내부의 고유 크기 조정 (intrinsic sizing)을 복구하자, 정렬 문제는 진정한 그리드 (grid) 이슈가 되었고, 이는 다음 개선 단계로 이어집니다.

그리드가 동일한 높이를 처리하게 하세요

고정 높이는 매력적으로 느껴졌습니다. 하지만 동일한 높이를 갖는다는 것이 실제로 높이를 수동으로 고정해야 한다는 의미는 아닙니다. 그리드는 제가 각 컴포넌트에 엄격한 경계 (hard boundaries)를 강요하지 않고도 우리를 대신해 정렬을 처리할 수 있습니다.

때로는 해결책이 놀라울 정도로 간단합니다. align-items: start를 제거하면 그리드 아이템 (grid items)이 자연스럽게 늘어나며, 더 유연한 열 정의 (column definition)로 전환하면 다양한 화면 크기에 따라 레이아웃이 더 잘 적응하도록 도와줍니다.

.card-grid {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

동일한 레이아웃이 고유 카드 높이 (intrinsic card heights)와 유연한 그리드 트랙 (flexible grid tracks)을 어떻게 사용하는지 확인해 보세요:

처음에 했던 것처럼 버튼을 깔끔하게 정렬하기 위해, 수동으로 위치를 잡고 공간을 확보하는 대신:

.card {
padding: 14px;
position: relative;
...

우리는 카드를 수직 레이아웃 (vertical layout)으로 바꿉니다:

.card {
display: flex;
flex-direction: column;
...

여기서 플렉스박스 (flexbox)를 깊게 다루지는 않겠습니다. Kevin Powell이 정확히 그 주제에 대해 훌륭한 글을 작성했기 때문입니다. 하지만 어떤 일이 일어나고 있는지는 알아둘 가치가 있습니다. flex-direction: column을 사용하여 카드를 플렉스 컨테이너 (flex container)로 만들면, 모든 요소가 위에서 아래로 수직으로 정렬됩니다.

다음 단계는 액션 (actions)을 위한 공간을 확보하기 위해 만들어 두었던 인위적인 공간을 제거하는 것입니다:

.card__body {
padding-block-end: 56px;
padding-block-start: 10px;
...

그 패딩 (padding)은 추측에 의한 것이었습니다. 콘텐츠가 예측 가능한 범위 내에 있을 때만 작동했습니다. 대신, 우리는 본문 (body)이 자연스럽게 확장되도록 합니다:

.card__body {
display: flex;
flex-direction: column;
...

flex: 1

flex: 1은 이미지와 액션(actions) 요소가 필요한 공간을 차지한 후, 남은 모든 공간을 바디(body)가 차지하도록 지시합니다.

태그(tags)에 약간의 여유 공간이 필요하다면, 간단한 마진(margin)만으로도 충분합니다:

.card__tags {
margin-block-end: 10px;
}

이제 원래 페이지와 똑같이 정렬된 카드를 얻을 수 있지만, 이 정렬은 높이를 강제로 지정하는 것이 아니라 레이아웃 흐름(layout flow)으로부터 나옵니다.

clamp()

유동적 타이포그래피 (Fluid Typography)를 위한 사용

clamp()를 사용한 유동적 타이포그래피 (Fluid typography)는 뷰포트(viewport) 크기에 따라 제목이 더 부드럽게 조절되도록 만들 수 있습니다:

.card__title {
font-size: clamp(1rem, 2vw, 1.25rem);
}

clamp()에 대해 더 자세히 알고 싶다면, CSS clamp()를 사용하여 글꼴 크기를 조절하는 방법에 관한 Pedro Rodriguez의 기사를 읽어보는 것을 추천합니다.

clamp(1rem, 2vw, 1.25rem)를 선언하면 제목이 안전한 범위 내에 머물면서 뷰포트에 따라 크기가 조절됩니다. 글꼴 크기는 뷰포트에 따라 커지거나 작아질 수 있지만 (2vw), 1rem보다 작아지거나 1.25rem보다 커지지는 않습니다.

실패를 대비한 설계 (Designing for Failure)

앞서 이 레이아웃에서 언급한 문제들은 제가 직접 구축할 때는 나타나지 않았습니다. 문제들은 특정 조건이 변할 때만 나타났습니다. 때로는 이미지가 로드되지 않아 카드의 수직 균형이 깨지기도 했고, 뷰포트가 좁아짐에 따라 텍스트가 더 공격적으로 줄바꿈(wrap)되어야 했습니다.

특정 컴포넌트가 실제 콘텐츠에서도 잘 버텨낼 수 있을지 알고 싶다면, 극한의 조건에 놓아보세요. 몇 가지 간단한 수정만으로도 레이아웃이 어디서부터 깨지거나 무너지는지 확인할 수 있습니다:

  • 브라우저의 기본 글꼴 크기를 키워 어떻게 동작하는지 확인합니다.
  • 페이지 확대/축소 대신 텍스트 전용 확대/축소를 활성화하여 차이점을 관찰합니다.
  • 제목을 끊어지지 않는 단일 문자열로 바꾸거나, 더 긴 단어를 사용하는 다른 언어를 시뮬레이션합니다.
  • 이미지가 없는 상황을 시뮬레이션합니다.
  • 텍스트가 공격적으로 줄바꿈되기 시작할 때까지 뷰포트를 줄입니다.

추상적으로 설명하기보다는, 이러한 요소들을 고유 높이(intrinsic-height) 버전의 카드에 직접 적용해 보겠습니다.

스트레스 테스트 모드 (Stress Test Mode)

유 높이(intrinsic-height) 버전에서, 몇 가지 콘텐츠 스트레스 케이스(content stress cases)를 시뮬레이션하는 간단한 토글(toggle)을 추가할 수 있습니다.

.demo-toolbar 내부에 다음 버튼을 추가하세요:

<button type="button" id="toggleStress">
Toggle stress test
</button>

다음 스크립트도 추가하세요:

const stressBtn = document.querySelector("#toggleStress");
stressBtn.addEventListener("click", () => {
document.body.classList.toggle("stress");
...

이 스크립트는 단순히 버튼 클릭을 감지하여 <body> 요소에 stress 클래스를 추가하거나 제거합니다. 해당 클래스는 스트레스 테스트 스타일을 켜고 끄는 스위치 역할을 합니다.

그리고 다음 스타일을 추가하세요:

body.stress .card:nth-child(1) .card__title::after {
content: "ExtremelyLongUnbrokenStringWithoutAnySpacesToTestOverflowBehavior";
}
...

이 스타일들은 몇 가지 흔한 레이아웃 스트레스 케이스를 시뮬레이션합니다. 첫 번째 카드는 오버플로(overflow) 동작을 테스트하기 위해 끊기지 않는 긴 문자열을 받습니다. 두 번째 카드는 더 큰 기본 글꼴 설정을 모방하기 위해 텍스트 크기를 키웁니다. .card__media img에 적용된 규칙은 미디어를 완전히 숨겨 이미지가 누락되었거나 로드에 실패한 상황을 시뮬레이션합니다.

이러한 안정성은 제가 마지막에 추가한 방어적인 규칙들에서 오는 것이 아닙니다. 그것은 앞서 내린 구조적인 결정들로부터 옵니다. 고정 높이(fixed heights)와 흐름을 벗어난 배치(out-of-flow positioning)가 제거되자, 컴포넌트는 어떤 콘텐츠를 받더라도 자연스럽게 적응할 수 있게 되었습니다.

내재적 크기 조절(intrinsic sizing)에 의존하기 시작하면, 가능한 모든 문자열 길이 나 글꼴 설정에 대해 걱정할 필요가 없어집니다. 콘텐츠가 길어지거나 텍스트 크기가 변경되어도 브라우저가 이를 처리할 수 있기 때문입니다. 대부분의 레이아웃 문제는 우리가 이러한 유연성을 제거할 때 발생합니다.

그렇다면, 무엇이 커지고 무엇이 커지지 않는가?

원래의 카드는 단순한 이유로 실패했습니다. 바로 명시되지 않은 가정들에 의존했기 때문입니다. 제목은 두 줄 안에 들어와야 했고, 발췌문은 네 줄 안에 들어와야 했으며, 버튼은 한 줄에 유지되어야 했습니다. 번역된 텍스트는 "대략 비슷한 길이"를 유지해야 했고, 사용자들은 기본 텍스트 설정을 그대로 유지할 것이라고 가정했습니다. 이 중 어느 것도 강제되지 않았습니다. 그것들은 그저 추측일 뿐이었습니다.

그러한 가정들은 조용히 저의 CSS에 스며들었습니다. 콘텐츠가 해당 경계 내에 머무는 동안에는 모든 것이 안정적으로 보였습니다. 하지만 콘텐츠가 경계를 벗어나는 순간, 레이아웃은 그 충돌에 대해 나쁜 반응을 보이기 시작했습니다.

이 컴포넌트 (Component)를 다시 구축할 때 제가 가장 먼저 한 일은 이러한 숨겨진 의존성 (Dependencies)을 제거하는 것이었습니다. 더 이상 고정된 픽셀 상한선도 없고, 끊임없이 조정해야 하는 패딩 (Padding) 여유분도 없으며, 레이아웃이 깨지는 것을 막기 위한 안전망 역할을 하는 말줄임 (Truncation)도 없습니다.

말줄임 (Truncation)은 여전히 의도적인 디자인 선택 사항이 될 수 있습니다. 하지만 단순히 레이아웃이 무너지는 것을 막기 위해 말줄임을 사용해서는 안 됩니다. 그런 상황이 발생한다면, 컴포넌트는 이미 과부하 상태에 놓인 것입니다.

최종 데모는 그 아이디어를 실제로 보여줍니다. 데모는 기본적으로 더 긴 번역 텍스트, 줄바꿈된 태그, 누락된 이미지 등 과부하된 콘텐츠를 로드하여, 이상적인 조건이 아닌 실제 조건에서 컴포넌트가 어떻게 작동하는지 확인할 수 있도록 합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 CSS-Tricks의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0