🔗 Align Items
The align-items property controls how flex items are aligned along the cross axis (the axis perpendicular to the main axis).
It defines the default alignment for all items in the container (unless overridden by align-self on individual items).
🧭 Cross Axis Reminder
| Main Axis | Cross Axis |
|---|---|---|
| horizontal ➡ | vertical ⬇ |
| vertical ⬇ | horizontal ➡ |
So align-items affects:
vertical alignment in a
rowhorizontal alignment in a
column
🧱 Syntax
<style>
.flex-container {
display: flex;
background-color: lightgrey;
padding: 10px;
height: 200px; /* Needed to visualize stretching */
}
.flex-item {
background: steelblue;
color: white;
margin: 5px;
padding: 20px;
text-align: center;
}
</style>
<div class="flex-container">
<div class="flex-item">1</div>
<div class="flex-item">2</div>
<div class="flex-item">3</div>
</div>align-items: flex-start | center | flex-end | stretch;🔹 Values Explained
🔸 stretch (default)
Items stretch to fill the container along the cross axis.
Only works if items have no fixed height/width.
.flex-container {
display: flex;
align-items: stretch;
}🔸 flex-start
Items align to the start of the cross axis.
.flex-container {
display: flex;
align-items: flex-start;
}🔸 flex-end
Items align to the end of the cross axis.
.flex-container {
display: flex;
align-items: flex-end;
}🔸 center
Items align to the center of the cross axis.
.flex-container {
display: flex;
align-items: center;
}🧱 Align-items Playground
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Align Items Playground</title>
<style>
.infographic-alignitems {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-alignitems[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.1rem; }
.ha-code {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
padding: 0.8rem 1.8rem 0.8rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.85rem;
color: #e5e5e5;
overflow-x: auto;
}
.ha-code .ha-prop { color: #c586c0; }
.ha-code .ha-val-hl { color: #ffd166; font-weight: 700; }
.ha-code .ha-sel { color: #f472b6; font-weight: 700; }
.ha-pills {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
}
.ha-pill {
border: 1px solid var(--line);
background: var(--paper);
color: var(--ink);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
padding: 0.45rem 0.85rem;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.ha-pill:hover { border-color: var(--accent); }
.ha-pill.ha-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.ha-toggle {
display: flex;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.25rem;
gap: 0.25rem;
}
.ha-toggle button {
flex: 1;
border: none;
background: transparent;
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
padding: 0.5rem 0.6rem;
border-radius: 999px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease;
}
.ha-toggle button.ha-on { background: var(--accent); color: #fff; }
.ha-stage {
background: lightgrey;
border-radius: 10px;
padding: 14px;
height: 220px;
display: flex;
box-sizing: border-box;
gap: 10px;
transition: height 0.3s ease;
}
.ha-stage.ha-col { height: 280px; }
.ha-flex-item {
background: steelblue;
color: white;
padding: 14px;
width: 70px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-weight: 700;
text-align: center;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.ha-note {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 0.78rem;
color: var(--ink-soft);
}
</style>
</head>
<body>
<div class="infographic-alignitems" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code" id="code-out"></div>
<div class="ha-toggle">
<button class="ha-on" id="btn-row">flex-direction: row</button>
<button id="btn-column">flex-direction: column</button>
</div>
<div class="ha-pills" id="align-pills">
<button class="ha-pill ha-on" data-val="stretch">stretch</button>
<button class="ha-pill" data-val="flex-start">flex-start</button>
<button class="ha-pill" data-val="center">center</button>
<button class="ha-pill" data-val="flex-end">flex-end</button>
</div>
<div class="ha-stage" id="stage">
<div class="ha-flex-item" id="item-1">1</div>
<div class="ha-flex-item" id="item-2">2</div>
<div class="ha-flex-item" id="item-3">3</div>
</div>
<div class="ha-note" id="note-out"></div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const stage = document.getElementById('stage');
const codeOut = document.getElementById('code-out');
const note = document.getElementById('note-out');
const items = [document.getElementById('item-1'), document.getElementById('item-2'), document.getElementById('item-3')];
const btnRow = document.getElementById('btn-row');
const btnColumn = document.getElementById('btn-column');
const nativeSizes = { 'item-1': 60, 'item-2': 140, 'item-3': 90 }; // px, used as height (row) or width (column)
const fixedCross = 70; // the other dimension stays constant
let direction = 'row';
let align = 'stretch';
const notesRow = {
'stretch': "The default. With no height set on the items, each one stretches to fill the container's full height — that's why all three currently look the same height.",
'flex-start': 'Each item keeps its own natural height, and all three align to the TOP of the container.',
'center': 'Each item keeps its own natural height, and all three center vertically — item 2 (the tallest) barely moves, while items 1 and 3 shift the most.',
'flex-end': 'Each item keeps its own natural height, and all three align to the BOTTOM of the container.'
};
const notesColumn = {
'stretch': "With flex-direction: column, the cross axis is now horizontal — stretch makes every item fill the container's full WIDTH instead of height.",
'flex-start': 'Each item keeps its own natural width, and all three align to the LEFT edge.',
'center': 'Each item keeps its own natural width, and all three center horizontally — item 2 (the widest) barely moves.',
'flex-end': 'Each item keeps its own natural width, and all three align to the RIGHT edge.'
};
function currentNotes() {
return direction === 'row' ? notesRow : notesColumn;
}
function applySizing() {
items.forEach(el => {
if (direction === 'row') {
el.style.width = fixedCross + 'px';
el.style.height = align === 'stretch' ? '' : nativeSizes[el.id] + 'px';
} else {
el.style.height = fixedCross + 'px';
el.style.width = align === 'stretch' ? '' : nativeSizes[el.id] + 'px';
}
});
}
function animateChange() {
const firstRects = items.map(el => el.getBoundingClientRect());
stage.classList.toggle('ha-col', direction === 'column');
stage.style.flexDirection = direction;
stage.style.alignItems = align;
applySizing();
void stage.offsetHeight;
const lastRects = items.map(el => el.getBoundingClientRect());
items.forEach((el, i) => {
const dx = firstRects[i].left - lastRects[i].left;
const dy = firstRects[i].top - lastRects[i].top;
const scaleX = lastRects[i].width ? firstRects[i].width / lastRects[i].width : 1;
const scaleY = lastRects[i].height ? firstRects[i].height / lastRects[i].height : 1;
el.style.transition = 'none';
el.style.transformOrigin = 'top left';
el.style.transform = 'translate(' + dx + 'px, ' + dy + 'px) scale(' + scaleX + ', ' + scaleY + ')';
});
requestAnimationFrame(() => {
items.forEach(el => {
el.style.transition = 'transform 0.4s cubic-bezier(0.22, 1, 0.36, 1)';
el.style.transform = 'translate(0, 0) scale(1, 1)';
});
});
codeOut.innerHTML = '<span class="ha-sel">.flex-container</span> {\n ' +
'<span class="ha-prop">display</span>: flex;\n ' +
'<span class="ha-prop">flex-direction</span>: <span class="ha-val-hl">' + direction + '</span>;\n ' +
'<span class="ha-prop">align-items</span>: <span class="ha-val-hl">' + align + '</span>;\n}';
note.textContent = currentNotes()[align];
}
btnRow.addEventListener('click', () => {
if (direction === 'row') return;
direction = 'row';
btnRow.classList.add('ha-on');
btnColumn.classList.remove('ha-on');
animateChange();
});
btnColumn.addEventListener('click', () => {
if (direction === 'column') return;
direction = 'column';
btnColumn.classList.add('ha-on');
btnRow.classList.remove('ha-on');
animateChange();
});
document.getElementById('align-pills').querySelectorAll('.ha-pill').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.ha-pill').forEach(p => p.classList.remove('ha-on'));
btn.classList.add('ha-on');
align = btn.getAttribute('data-val');
animateChange();
});
});
animateChange();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});✅ Summary
align-itemscontrols vertical or horizontal alignment depending on main axis.Default is
stretch, but you can easily center or align to edges.Use with
justify-contentfor full 2D layout control.