줄이 맞은 화면은 그것만으로 정돈된 인상을 줍니다. 시작선이 어긋난 목록과 격자로 딱 떨어진 목록의 차이를 CSS로 하나씩 만들어 보겠습니다. 아래 코드는 그대로 복사해 브라우저에서 확인할 수 있습니다.
02.1 시작선 하나로 맞추기
라벨과 값이 제각각 시작하면 눈이 세로선을 따라가지 못합니다. flex로 한 축에 세우고 라벨 폭을 고정해 봅니다.
.row {
display: flex;
align-items: baseline;
gap: 12px;
}
.row .label {
width: 84px;
color: #6b7280;
flex-shrink: 0;
}
라벨 폭을 84px로 고정하고 flex-shrink를 0으로 두면, 값이 길어져도 라벨 칸이 밀리지 않아 여러 줄의 값 시작선이 하나로 모입니다. baseline 정렬 덕에 글자 밑선까지 나란해집니다.
02.2 남는 공간 나눠 갖기
왼쪽 제목은 붙이고 오른쪽 버튼은 끝으로 밀고 싶을 때, 마진으로 억지로 미는 대신 flex의 빈 공간을 씁니다.
.bar {
display: flex;
align-items: center;
gap: 8px;
}
.bar .spacer {
margin-left: auto;
}
margin-left를 auto로 주면 그 요소부터 오른쪽으로 밀려 나머지 공간을 전부 차지합니다. 창을 넓혀도 좁혀도 제목은 왼쪽, 버튼은 오른쪽 끝을 지킵니다.
.bar .grow {
flex: 1;
min-width: 0;
}
가운데 요소에 flex: 1을 주면 남는 폭을 혼자 먹습니다. min-width: 0을 함께 두는 이유는 긴 텍스트가 칸을 밀어 넘치는 사고를 막기 위해서입니다.
02.3 그리드로 격자 짜기
카드 목록을 flex로 감싸면 마지막 줄 정렬이 어긋나기 쉽습니다. 같은 크기 칸이 반복될 땐 grid가 더 정직합니다.
.list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
1fr을 세 번 반복하면 폭을 정확히 삼등분한 칸이 생깁니다. gap 하나로 가로세로 간격이 동시에 잡히니 카드마다 마진을 붙일 필요가 없습니다.
02.4 화면 폭에 맞춰 칸 수 바꾸기
칸 수를 3으로 못 박으면 좁은 화면에서 카드가 눌려 찌그러집니다. 최소 폭만 정하고 칸 수는 브라우저에 맡깁니다.
.list {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
minmax(220px, 1fr)은 칸이 220px보다 좁아지지 않게 지키고, auto-fit이 폭에 들어갈 만큼 칸 수를 알아서 늘리고 줄입니다. 넓은 화면은 네댓 칸, 폰은 한 칸으로 미디어쿼리 없이 접힙니다.
02.5 두 축을 한 번에 세우기
아이콘 하나를 상자 정중앙에 두는 일은 예전엔 은근히 번거로웠습니다. 지금은 두 줄이면 끝납니다.
.center {
display: grid;
place-items: center;
min-height: 160px;
}
place-items: center 한 줄이 가로 정렬과 세로 정렬을 동시에 처리합니다. 자식이 몇 개든 각 칸 안에서 정중앙에 놓입니다.
02.6 영역 이름으로 레이아웃 그리기
헤더, 사이드바, 본문, 푸터 같은 큰 뼈대는 이름을 붙여 그림 그리듯 배치하면 한눈에 읽힙니다.
.page {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-areas:
"head head"
"side main"
"foot foot";
gap: 12px;
}
.page .head { grid-area: head; }
.page .side { grid-area: side; }
.page .main { grid-area: main; }
.page .foot { grid-area: foot; }
따옴표 안의 글자 배치가 그대로 화면 구조가 됩니다. head와 foot은 두 칸을 가로지르고 side와 main은 가운데 줄을 나눠 갖습니다. 코드만 봐도 레이아웃이 그려집니다.
02.7 좁아지면 한 줄로 접기
사이드바가 있는 배치는 폰에서 답답합니다. 좁아지면 영역 그림을 다시 그려 세로로 쌓습니다.
@media (max-width: 600px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"head"
"main"
"side"
"foot";
}
}
칸을 하나로 줄이고 영역 순서만 다시 적으면 본문이 사이드바보다 먼저 오도록 순서까지 바꿀 수 있습니다. 마크업은 그대로 두고 배치만 갈아 끼운 셈입니다.
02.8 카드 안 요소를 위아래로 밀착
카드 높이가 제각각이면 버튼 줄이 들쭉날쭉 떠 보입니다. 카드 자체를 세로 flex로 만들어 버튼을 바닥에 붙입니다.
.card {
display: flex;
flex-direction: column;
height: 100%;
}
.card .body {
flex: 1;
}
.card .foot {
margin-top: auto;
}
본문에 flex: 1을 줘서 남는 세로 공간을 밀어 올리면, 내용 길이가 달라도 모든 카드의 버튼 줄이 바닥에 딱 붙어 한 줄로 정렬됩니다. margin-top: auto만으로도 같은 효과를 냅니다.
02.9 특정 칸만 넓게 차지하기
격자 안에서 한 요소만 두 칸을 차지하게 하고 싶을 때가 있습니다. span으로 칸 수를 지정합니다.
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.grid .wide {
grid-column: span 2;
}
.grid .full {
grid-column: 1 / -1;
}
span 2는 인접한 두 칸을 하나로 합치고, 1 / -1은 첫 선부터 마지막 선까지 한 줄 전체를 채웁니다. 목록 안에 배너 하나만 가로로 길게 넣을 때 자주 쓰는 방식입니다.
02.10 정렬을 하나로 모으기
지금까지의 조각을 한 카드에 얹으면 이렇게 맞물립니다. 마크업은 단순하고 배치는 CSS가 도맡습니다.
<div class="list">
<article class="card">
<h3>제목</h3>
<p>설명 문장</p>
</article>
</div>
.card {
display: grid;
gap: 6px;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 10px;
}
바깥 grid가 카드를 격자에 세우고 안쪽 grid가 제목과 설명의 간격을 잡습니다. 시작선은 저절로 맞고 칸 수는 화면 폭을 따라갑니다. 줄을 맞추는 일에 더는 손으로 마진을 세지 않아도 됩니다. flex는 한 줄 안의 관계를, grid는 화면 전체의 골격을 다룬다고 기억해 두면 둘 중 무엇을 꺼낼지 헷갈리지 않습니다.