한 문장씩으로 보는 justify-content와 align-items
flex 컨테이너에서 justify-content는 항목을 주축 (행을 가로지르는 방향)을 따라 펼치고, align-items는 항목을 교차축 (행의 위아래 방향)에서 맞춰요. 둘 다 컨테이너에 줘요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Center both ways</title>
<style>
.box {
display: flex;
justify-content: center; /* horizontal, in a row */
align-items: center; /* vertical, in a row */
height: 140px;
background: #e5e7eb;
}
.item { background: #2563eb; color: white; padding: 12px 18px; }
</style>
</head>
<body>
<div class="box"><div class="item">Centered</div></div>
</body>
</html>Master CSS the hands-on way
Interactive lessons, real projects, and instant feedback.
START NOW
상자는 양쪽으로 가운데 정렬되어 있어요. 시작과 끝 가장자리에서, 그리고 위와 아래에서 같은 거리예요. height: 140px를 빼면 컨테이너가 항목 자신의 높이로 줄어들어서 align-items: center가 할 일이 없어져요.
모든 justify-content 값, 측정해서
아래의 각 행은 같은 세 항목에 다른 justify-content를 준 거예요. 각 행 위 레이블의 숫자는 브라우저가 남긴 간격이에요. 첫 항목 앞, 항목 사이, 마지막 항목 뒤예요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>justify-content values</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; width: 420px; background: #e5e7eb; margin: 2px 0 10px; }
.row div { width: 60px; height: 28px; background: #2563eb; }
.row div:nth-child(2) { background: #16a34a; }
.row div:nth-child(3) { background: #f59e0b; }
</style>
</head>
<body>
<div id="list"></div>
<script>
const values = ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'];
const list = document.getElementById('list');
values.forEach((v) => {
const label = document.createElement('code');
const row = document.createElement('div');
row.className = 'row';
row.style.justifyContent = v;
row.innerHTML = '<div></div><div></div><div></div>';
list.append(label, row);
const b = row.getBoundingClientRect();
const r = [...row.children].map((c) => c.getBoundingClientRect());
const gaps = [r[0].left - b.left, r[1].left - r[0].right, r[2].left - r[1].right, b.right - r[2].right];
label.textContent = 'justify-content: ' + v + ' gaps: ' + gaps.map(Math.round).join(' / ');
});
</script>
</body>
</html>
컨테이너는 420px이고 항목이 180px을 차지해서 빈 공간이 240px이에요. 그 공간이 가는 곳이에요.
값 빈 공간이 가는 곳 데모의 간격 flex-start (그리고 start)모두 항목 뒤로 0 / 0 / 0 / 240 center절반은 앞, 절반은 뒤 120 / 0 / 0 / 120 flex-end (그리고 end)모두 항목 앞으로 240 / 0 / 0 / 0 space-between항목 사이에만 0 / 120 / 120 / 0 space-around각 항목 양쪽에 같게, 그래서 가장자리는 절반 40 / 80 / 80 / 40 space-evenly가장자리 포함 모든 간격을 같게 60 / 60 / 60 / 60
flex-start는 flex 방향을, start는 글쓰기 방향을 따라요. 둘은 row-reverse나 column-reverse에서만 달라지고, 이때 flex-start는 뒤집힌 쪽 끝에 있어요.
모든 align-items 값
align-items는 행을 가로질러 동작해요. 아래 항목들은 높이가 달라서 각 값이 항목을 어떻게 다루는지 볼 수 있어요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>align-items values</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.grid { display: flex; flex-wrap: wrap; gap: 16px; }
.row { display: flex; gap: 4px; width: 170px; height: 100px; background: #e5e7eb; }
.row div { width: 50px; background: #2563eb; color: white; }
.row div:nth-child(1) { font-size: 26px; }
.row div:nth-child(2) { background: #16a34a; padding-top: 20px; }
.row div:nth-child(3) { background: #f59e0b; }
</style>
</head>
<body>
<div class="grid" id="grid"></div>
<script>
['stretch', 'flex-start', 'center', 'flex-end', 'baseline'].forEach((v) => {
const cell = document.createElement('div');
cell.innerHTML = '<code>align-items: ' + v + '</code><div class="row" style="align-items: ' + v + '"><div>Ag</div><div>Ag</div><div>Ag</div></div>';
document.getElementById('grid').append(cell);
});
</script>
</body>
</html>
값 교차축에서 일어나는 일 stretch (기본값)높이가 지정되지 않은 항목이 컨테이너를 채우도록 늘어나요 flex-start (그리고 start)항목이 자기 높이로 위쪽에 놓여요 center항목이 자기 높이로 가운데 정렬돼요 flex-end (그리고 end)항목이 아래쪽에 놓여요 baseline텍스트 첫 줄이 한 기준선을 공유하도록 항목이 움직여요
baseline 상자에서는 첫 번째가 더 크고 두 번째는 위에 padding이 있는데도 세 "Ag" 레이블이 한 줄에 놓여요. 큰 제목과 작은 텍스트, 또는 레이블과 입력 요소를 섞은 행에 쓰는 값이에요.
클릭해서 정렬하기
3 x 3 격자의 아무 칸이나 클릭해 보세요. 상자가 그리로 이동하고, 그 위치를 만드는 CSS가 아래에 출력돼요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Alignment picker</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 14px; }
.wrap { display: flex; gap: 16px; align-items: flex-start; }
.picker { display: grid; grid-template-columns: repeat(3, 28px); gap: 4px; }
.picker button { height: 28px; border: 1px solid #111827; background: white; cursor: pointer; }
.picker button[aria-pressed="true"] { background: #2563eb; }
#stage { display: flex; width: 260px; height: 150px; background: #e5e7eb; }
.item { background: #16a34a; color: white; padding: 10px 14px; }
pre { background: #f3f4f6; padding: 8px; }
</style>
</head>
<body>
<div class="wrap">
<div class="picker" id="picker"></div>
<div id="stage"><div class="item">Box</div></div>
</div>
<pre id="out"></pre>
<script>
const spots = ['flex-start', 'center', 'flex-end'];
const stage = document.getElementById('stage');
const pick = (row, col, btn) => {
stage.style.alignItems = spots[row];
stage.style.justifyContent = spots[col];
document.querySelectorAll('.picker button').forEach((b) => b.setAttribute('aria-pressed', b === btn));
document.getElementById('out').textContent =
'.stage {\n display: flex;\n justify-content: ' + spots[col] + ';\n align-items: ' + spots[row] + ';\n}';
};
spots.forEach((_, row) => spots.forEach((_, col) => {
const btn = document.createElement('button');
btn.setAttribute('aria-label', 'row ' + (row + 1) + ', column ' + (col + 1));
btn.addEventListener('click', () => pick(row, col, btn));
document.getElementById('picker').append(btn);
}));
pick(1, 1, document.querySelectorAll('.picker button')[4]);
</script>
</body>
</html>
선택기의 행은 align-items를, 열은 justify-content를 바꿔요. 가운데 칸이 고전적인 "div 가운데 정렬"이에요.
열에서는 둘이 바뀌어요
justify-content는 항상 주축을 따라가요. flex-direction: column이면 주축이 세로라서 justify-content가 항목을 위아래로, align-items가 가로로 움직여요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Column axes</title>
<style>
.col {
display: flex;
flex-direction: column;
justify-content: flex-end; /* now vertical: pushed to the bottom */
align-items: center; /* now horizontal: centered */
height: 160px;
background: #e5e7eb;
}
.col div { background: #2563eb; color: white; padding: 6px 12px; }
</style>
</head>
<body>
<div class="col"><div>One</div><div>Two</div></div>
</body>
</html>
헷갈렸다면 레이아웃을 다시 쓰지 말고 두 속성을 맞바꾸세요.
align-self와 auto margin: 항목 하나만
항목에 align-self를 주면 그 항목에 대해 align-items를 덮어써요. flexbox에는 justify-self가 없어요. 주축을 따라 한 항목을 움직이려면 auto margin을 주세요. 그쪽의 빈 공간을 모두 차지해요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>align-self</title>
<style>
.bar { display: flex; align-items: flex-start; gap: 8px; height: 90px; background: #e5e7eb; }
.bar div { background: #2563eb; color: white; padding: 8px 12px; }
.bar .down { align-self: flex-end; background: #16a34a; }
.bar .last { margin-inline-start: auto; background: #f59e0b; }
</style>
</head>
<body>
<div class="bar">
<div>Top</div>
<div class="down">align-self: flex-end</div>
<div class="last">margin-inline-start: auto</div>
</div>
</body>
</html>
초록 항목은 혼자 아래로 내려가고, 주황 항목은 행의 반대쪽 끝으로 밀려나며, 나머지는 시작 쪽에 머물러요.
줄이 여러 개일 때: align-content
align-items는 줄 안에서 항목을 정렬해요. 컨테이너가 여러 줄로 줄바꿈되면 align-content가 줄 자체의 간격을 정하고, justify-content와 같은 값을 받아요. 줄이 하나뿐이라 항상 컨테이너 높이를 채우는 nowrap 컨테이너에서는 아무 일도 하지 않아요. flex-wrap 페이지 에서 보여 줘요.
자주 하는 실수
축 헷갈리기. justify-content는 흐르는 방향, align-items는 그 가로 방향이에요. 열에서 세로 가운데 정렬은 justify-content: center예요.
빈 공간 없음. flex: 1 항목이 공간을 모두 가져가서 justify-content가 분배할 게 없어요. 컨테이너가 inline-flex이고 항목과 정확히 같은 너비일 때도 마찬가지예요.
align-items에 필요한 높이 없음. 콘텐츠만큼만 높은 컨테이너에는 정렬할 게 남지 않아요. height나 min-height를 지정하세요.
flexbox에서 justify-self 찾기. 거기서는 무시돼요. 항목에 margin-inline-start: auto(또는 margin-left: auto)를 쓰세요.
늘어나지 않는 align-items: stretch. 자기 height가 있는 항목은 그걸 유지해요. 높이를 지우세요(최솟값이 필요하면 min-height를 쓰세요).
항목에 속성 주기. 둘 다 flex 컨테이너에 줘요. 항목에 주는 건 align-self뿐이에요.