카드는 관련된 정보 한 덩어리를 한 장에 묶는 그릇이고, 리스트는 같은 것들을 위에서 아래로 세우는 그릇입니다. 저는 이 둘을 만들 때 대부분의 일을 CSS가 대신하게 둡니다. 아래 예제로 카드 한 장부터 반응형 그리드까지 코드로 쌓아 보겠습니다.

13.1 카드 기본 골격

카드는 테두리나 그림자로 안쪽 내용을 한 식구로 묶습니다. 여백을 넉넉히 둬야 답답하지 않습니다.


.card {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}


옅은 테두리와 은은한 그림자가 카드를 배경에서 살짝 띄웁니다. 그림자를 진하게 주지 않은 이유는, 여러 장을 늘어놓으면 진한 그림자가 금세 소음이 되기 때문입니다.

13.2 카드 안의 위계

카드 안에서도 제목과 설명이 같은 무게로 소리치면 어수선합니다. 크기와 색으로 순서를 매깁니다.


.card {
display: flex;
flex-direction: column;
gap: 8px;
}
.card h3 {
font-size: 18px;
font-weight: 700;
color: #111827;
}
.card p {
font-size: 14px;
color: #6b7280;
}


제목이 먼저 눈에 들어오고 회색 설명이 뒤따릅니다. flex의 gap이 요소마다 일정한 틈을 줘서 손으로 margin을 매번 넣지 않아도 됩니다.

13.3 이미지가 있는 카드

썸네일이 카드 위에 얹히는 형태는 흔합니다. 이미지가 테두리 라운드를 넘지 않게 다듬는 게 관건입니다.


.card {
overflow: hidden;
padding: 0;
}
.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.card .body {
padding: 16px;
}


aspect-ratio로 비율을 고정하고 object-fit: cover로 잘라 채우면, 원본 크기가 제각각인 이미지도 카드마다 같은 높이로 앉습니다. overflow: hidden이 라운드 밖으로 삐져나온 모서리를 잘라 줍니다.

13.4 카드 그리드로 늘어놓기

카드 여러 장은 grid로 깔면 줄이 자동으로 맞습니다. 개수를 일일이 세지 않아도 됩니다.


.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}


세 열이 같은 너비로 나뉘고 간격이 일정하게 유지됩니다. 카드 사이 간격을 카드 안쪽 여백과 비슷하게 두면 한 장 안은 뭉치고 장과 장은 갈라져 보입니다.

13.5 반응형 그리드

열 개수를 화면마다 미디어쿼리로 바꿔도 되지만, auto-fill을 쓰면 브라우저가 알아서 접어 줍니다.


.grid {
display: grid;
grid-template-columns:
repeat(auto-fill, minmax(240px, 1fr));
gap: 16px;
}


카드 최소 너비를 240px로 정하면, 공간이 남는 만큼 열이 늘고 좁아지면 저절로 줄어 결국 한 열이 됩니다. 미디어쿼리 없이 한 줄로 반응형을 끝낸 셈입니다.

13.6 리스트 항목 정렬

리스트는 각 항목이 같은 틀을 가져야 눈이 세로로 훑기 좋습니다. 왼쪽에 아이콘, 가운데에 내용, 오른쪽에 값을 두는 형태가 흔합니다.


.item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 8px;
}
.item .main { flex: 1; }
.item .meta {
color: #9ca3af;
font-size: 13px;
}


가운데 영역에 flex: 1을 주면 남는 공간을 다 차지해서, 오른쪽 값이 항상 끝에 붙습니다. 항목마다 이 규칙이 같으니 값들이 세로로 한 줄에 정렬됩니다.

13.7 항목 나누기

항목을 가르는 데는 진한 선보다 여백과 옅은 구분선이 낫습니다. 선이 굵으면 창살처럼 갑갑해집니다.


.item + .item {
border-top: 1px solid #f3f4f6;
}
.item:hover {
background: #f9fafb;
}


인접 선택자로 두 번째 항목부터만 위에 선을 그으면, 첫 항목 위와 마지막 항목 아래에 군더더기 선이 생기지 않습니다. hover 배경은 지금 어느 줄에 손이 있는지 짚어 줍니다.

13.8 긴 제목 말줄임

제목 길이가 항목마다 다르면 리스트 리듬이 흐트러집니다. 한 줄로 넘치는 부분은 말줄임으로 갈무리합니다.


.item .title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}


넘치는 글자가 점 세 개로 잘려 모든 줄의 높이가 같아집니다. 다만 뒤에 꼭 봐야 할 정보가 잘리지는 않는지 살펴야 하니, 말줄임은 훑어보는 제목에만 씁니다.

13.9 빈 목록 안내

목록이 텅 비었을 때 아무것도 안 보이면 사용자는 고장으로 오해합니다. 무엇을 채우면 되는지 짧게 안내합니다.


.empty {
text-align: center;
padding: 48px 16px;
color: #9ca3af;
}
.empty .cta {
margin-top: 12px;
color: #2563eb;
}


가운데 정렬한 안내와 파란 행동 유도 문구가 빈 화면을 기회로 바꿉니다. 첫 사용자가 여기서 무엇을 해야 할지 바로 알게 됩니다.

13.10 카드에 배지와 버튼

카드 한 장에 상태 배지와 행동 버튼을 얹을 때는 주된 행동 하나만 도드라지게 둡니다. 여러 버튼이 같은 무게로 서면 어디를 눌러야 할지 헷갈립니다.


.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
background: #dcfce7;
color: #166534;
}
.card .cta {
margin-top: 12px;
align-self: flex-start;
background: #2563eb;
color: #fff;
padding: 8px 14px;
border-radius: 8px;
}


알약 모양 배지가 상태를 조용히 알리고, 파란 버튼 하나가 다음 행동을 이끕니다. align-self: flex-start로 버튼이 카드 폭을 다 먹지 않고 내용만큼만 차지하게 했습니다.

13.11 카드 마크업 전체

스타일이 붙을 뼈대를 마크업으로 보면 이렇게 됩니다. 클래스 이름이 곧 각 조각의 역할을 말해 줍니다.


<article class="card">
<img src="cover.jpg" alt="표지">
<div class="body">
<span class="badge">신규</span>
<h3>주말 캠핑 가이드</h3>
<p>초보도 따라 하는 준비물 목록</p>
<a class="cta" href="/guide">읽기</a>
</div>
</article>


article 태그로 감싸면 카드 하나가 독립된 콘텐츠 조각임이 구조에도 드러납니다. img의 alt는 그림을 못 보는 사용자에게 표지의 내용을 대신 전합니다.

13.12 정리

카드는 한 덩어리를 묶어 돋보이게 하고, 리스트는 같은 것들을 세워 훑게 합니다. grid와 flex가 정렬과 반응형을 대신해 주니, 우리는 무엇을 묶고 무엇을 나눌지만 정하면 됩니다. 화려한 그릇을 먼저 고르기보다, 사용자가 이 내용을 훑을지 하나씩 볼지를 헤아려 그릇을 정하면 화면이 덜 어수선해집니다.