CSS Module 11 – CSS Grid
Grid alignment, overlap and new grid features
Align grid items and tracks with justify-items, align-content and place-items, overlap items in one cell, and add grid lanes and gap rules as enhancements.
What you will learn
- Use justify-items, align-items, place-content and place-self to align items and tracks
- Build overlays by placing several items in one cell, without position absolute
- Apply grid lanes (masonry) and gap decorations as progressive enhancements
Before you start
What you’ll learn · Before you start
What you will learn
- Use justify-items, align-items, place-content and place-self to align items and tracks
- Build overlays by placing several items in one cell, without position absolute
- Apply grid lanes (masonry) and gap decorations as progressive enhancements
Before you start
On this page
Grid has two kinds of alignment. justify-items and align-items (or place-items for both) move every item inside
its own cell: across and down. justify-content and align-content move the tracks themselves when they do not fill
the container. So place-items: center on a grid is the shortest way to centre a child in both directions, and
justify-content: space-between spreads fixed-width columns across a wide container. Grid can also put several items
in the same cell on purpose, which builds overlays such as a caption over a photo without position: absolute and
without its main failure: text that grows past the photo. Two newer features build on the grid: grid lanes, the
masonry layout, and lines drawn in the gaps. Each works in only one engine so far, so they belong in progressive
enhancements. Positions and sizes quoted here were taken from Chromium 153, Firefox 155 and WebKit 26.6.
Two kinds of alignment
The property names follow one pattern. justify works along the inline axis, across a line of English text;
align works along the block axis, down the page. -items sets the default for every item in the container,
-self overrides it on one item, and -content moves the tracks. The place- shorthands take the align value
first, then the justify value.
The nine combinations of align-items and justify-items
Text description of the diagram
A table of nine grid cells, each holding one small item.
- Across the top, the columns are labelled justify-items: start, center and end, which move the item across the cell, along the inline axis.
- Down the side, the rows are labelled align-items: start, center and end, which move the item down the cell, along the block axis.
- In each cell the item sits where its row and column say: in the top-left cell at the top-left corner, in the middle cell in the centre, in the bottom-right cell at the bottom-right corner, and so on.
- A note under the table says that the default value, normal, stretches the item over the whole cell, while images and other items with an aspect ratio keep their size at the start.
- The last line shows the shorthand: place-items takes the align value first, then the justify value.
Aligning items in their cells
One item, a 31 px wide word, in a single 200 by 120 px cell, with different values on the container; then an image with the default; then an item taller than its row:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>One item in a 200 by 120 px cell, aligned five ways</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.cell { display: grid; grid-template: 120px / 200px; margin-block-end: 12px; background: #eee; }
.cell > * { background: #dde; }
#normal { } /* the default: normal, which stretches the item */
#start { place-items: start; }
#center { place-items: center; }
#end { place-items: end; }
#mixed { justify-items: end; align-items: center; } /* across: end; down: centre */
#image { } /* an image, with the default alignment */
/* An item taller than its row, centred: the row is 120px, the item 200px. */
.short-row { display: grid; grid-template: 120px / 200px; margin-block: 120px 12px; background: #eee; }
.short-row > * { block-size: 200px; background: #dde; }
#unsafe { align-items: center; }
#safe { align-items: safe center; }
</style>
</head>
<body>
<div class="cell" id="normal"><span>Item</span></div>
<div class="cell" id="start"><span>Item</span></div>
<div class="cell" id="center"><span>Item</span></div>
<div class="cell" id="end"><span>Item</span></div>
<div class="cell" id="mixed"><span>Item</span></div>
<div class="cell" id="image"><img alt="" width="60" height="40" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='40'/%3E"></div>
<div class="short-row" id="unsafe"><span>Tall item</span></div>
<div class="short-row" id="safe"><span>Tall item</span></div>
<script>
// Each item's left and top edges inside its cell, and its size, in px.
const round = (n) => Math.round(n * 100) / 100;
for (const grid of document.querySelectorAll('.cell, .short-row')) {
const g = grid.getBoundingClientRect();
const r = grid.firstElementChild.getBoundingClientRect();
console.log(`${grid.id}: x ${round(r.left - g.left)}, y ${round(r.top - g.top)}, ${round(r.width)} × ${round(r.height)}`);
}
</script>
</body>
</html> | Container | Item’s left and top edges | Item’s size |
|---|---|---|
nothing (normal) |
0, 0 | 200 × 120 px |
place-items: start |
0, 0 | 31.13 × 24 px |
place-items: center |
84.44, 48 | 31.13 × 24 px |
place-items: end |
168.88, 96 | 31.13 × 24 px |
justify-items: end; align-items: center |
168.88, 48 | 31.13 × 24 px |
| nothing, holding a 60 by 40 px image | 0, 0 | 60 × 40 px |
align-items: center, item 200 px tall in a 120 px row |
0, −40 | 200 × 200 px |
align-items: safe center, the same item |
0, 0 | 200 × 200 px |
All three engines agreed, to a hundredth of a pixel. Four rules come out of the table:
- The default stretches.
normalmakes an ordinary item fill its cell, which is why grid cards are equal height without any extra CSS. Any other value sizes the item to its content. - Images do not stretch by default. An item with an aspect ratio, such as an image or a video, treats
normalasstartand keeps its own size. Give itinline-size: 100%; block-size: 100%andobject-fit: coverto fill the cell. place-items: centercentres in both directions, whatever the size of the child; in a container at least as tall as the screen, it centres a single panel on the page.- Centring can push content out of reach. The tall item centred in a short row started 40 px above it. If that row
is the top of a scrolling area, those 40 px can never be scrolled to.
safe centerfalls back to the start whenever the item would overflow.
justify-self and align-self (or place-self) do the same for one item, overriding the container’s value.
Distributing the tracks
When tracks have fixed or content-based sizes that add up to less than the container, the leftover space can go before, between or around them. Three 100 px columns in a 600 px grid:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Three 100px columns in a 600px grid</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.tracks { display: grid; inline-size: 600px; grid-template-columns: repeat(3, 100px); margin-block-end: 12px; background: #eee; }
.tracks > * { background: #dde; }
.start { justify-content: start; }
.center { justify-content: center; }
.end { justify-content: end; }
.space-between { justify-content: space-between; }
.space-around { justify-content: space-around; }
.space-evenly { justify-content: space-evenly; }
</style>
</head>
<body>
<div class="tracks start"><div>1</div><div>2</div><div>3</div></div>
<div class="tracks center"><div>1</div><div>2</div><div>3</div></div>
<div class="tracks end"><div>1</div><div>2</div><div>3</div></div>
<div class="tracks space-between"><div>1</div><div>2</div><div>3</div></div>
<div class="tracks space-around"><div>1</div><div>2</div><div>3</div></div>
<div class="tracks space-evenly"><div>1</div><div>2</div><div>3</div></div>
<script>
// Where each column starts, from the grid's left edge.
for (const grid of document.querySelectorAll('.tracks')) {
const left = grid.getBoundingClientRect().left;
const starts = [...grid.children].map((c) => Math.round((c.getBoundingClientRect().left - left) * 100) / 100);
console.log(`justify-content: ${grid.classList[1]} -> columns start at ${starts.join(', ')}`);
}
</script>
</body>
</html> // Where justify-content puts the columns when they are narrower than the grid (CSS Box Alignment Level 3, content
// distribution): the free space is the grid's width minus the tracks and gaps, and each value spends it differently.
function columnStarts(gridWidth, tracks, gap, value) {
const used = tracks.reduce((sum, t) => sum + t, 0) + gap * (tracks.length - 1);
const free = Math.max(0, gridWidth - used);
const n = tracks.length;
const [before, between] = {
'start': [0, 0],
'center': [free / 2, 0],
'end': [free, 0],
'space-between': [0, n > 1 ? free / (n - 1) : 0],
'space-around': [free / n / 2, free / n],
'space-evenly': [free / (n + 1), free / (n + 1)],
}[value];
const starts = [];
let x = before;
for (const t of tracks) {
starts.push(Math.round(x * 100) / 100);
x += t + gap + between;
}
return starts;
}
for (const value of ['start', 'center', 'end', 'space-between', 'space-around', 'space-evenly']) {
console.log(`${value.padEnd(13)} three 100px columns in 600px start at ${columnStarts(600, [100, 100, 100], 0, value).join(', ')}`);
} Output
start three 100px columns in 600px start at 0, 100, 200 center three 100px columns in 600px start at 150, 250, 350 end three 100px columns in 600px start at 300, 400, 500 space-between three 100px columns in 600px start at 0, 250, 500 space-around three 100px columns in 600px start at 50, 250, 450 space-evenly three 100px columns in 600px start at 75, 250, 425
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node distribute.mjs
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The program prints exactly what the three engines drew. space-between puts the 300 px of free space between the
columns only, space-around gives each column half a share on each side (so the outer spaces are half the inner ones),
and space-evenly makes all four spaces equal at 75 px. align-content does the same down the page when a grid is
taller than its rows, for example a full-height page whose content should sit in the middle. With fr tracks there is
no free space left to distribute, so these properties change nothing.
Overlap: several items in one cell
Items placed in the same cell overlap, and grid aligns each of them separately. For a banner with a photo, a dark gradient over it (a scrim, so white text stays readable) and a caption, every child gets the same area and its own alignment. The example builds the same banner three ways, with a short English caption and with a longer Hindi one:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Text over a photo: one grid cell, or absolute positioning</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.hero { margin-block: 0 48px; color: #fff; }
.hero img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
.hero .scrim { background: linear-gradient(transparent 30%, rgb(0 0 0 / 0.7)); }
.hero .caption { margin: 0; padding: 16px; font-size: 24px; line-height: 32px; }
.hero .badge { margin: 8px; padding: 0 8px; background: #b3261e; }
/* Overlap in one cell: every child is placed in the same area, then aligned in it. */
.stack { display: grid; grid-template-areas: "hero"; }
.stack > * { grid-area: hero; }
.stack .caption { align-self: end; justify-self: start; }
.stack .badge { align-self: start; justify-self: end; }
/* Overlap with two rows: the photo and scrim cover both, the badge and the caption each get their own. */
.rows { display: grid; grid-template-rows: auto 1fr; }
.rows img, .rows .scrim { grid-row: 1 / -1; grid-column: 1; }
.rows .badge { grid-row: 1; grid-column: 1; align-self: start; justify-self: end; }
.rows .caption { grid-row: 2; grid-column: 1; align-self: end; }
/* The same design with absolute positioning. */
.absolute { position: relative; }
.absolute .scrim { position: absolute; inset: 0; }
.absolute .caption { position: absolute; inset-inline-start: 0; inset-block-end: 0; }
.absolute .badge { position: absolute; inset-block-start: 0; inset-inline-end: 0; }
</style>
</head>
<body>
<div class="hero stack" id="grid-short">
<img alt="Kurtas on a market stall" width="1600" height="600" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1600' height='600'%3E%3Crect width='1600' height='600' fill='%23345'/%3E%3C/svg%3E">
<div class="scrim"></div>
<p class="caption">Diwali sale</p>
<span class="badge">New</span>
</div>
<div class="hero stack" id="grid-long">
<img alt="Kurtas on a market stall" width="1600" height="600" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1600' height='600'%3E%3Crect width='1600' height='600' fill='%23345'/%3E%3C/svg%3E">
<div class="scrim"></div>
<p class="caption" lang="hi">दिवाली सेल: हाथ से बुने कुर्ते, साड़ियाँ और दुपट्टे, देश भर के कारीगरों से, हर ऑर्डर पर उपहार पैकिंग के साथ</p>
<span class="badge">New</span>
</div>
<div class="hero absolute" id="absolute-long">
<img alt="Kurtas on a market stall" width="1600" height="600" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1600' height='600'%3E%3Crect width='1600' height='600' fill='%23345'/%3E%3C/svg%3E">
<div class="scrim"></div>
<p class="caption" lang="hi">दिवाली सेल: हाथ से बुने कुर्ते, साड़ियाँ और दुपट्टे, देश भर के कारीगरों से, हर ऑर्डर पर उपहार पैकिंग के साथ</p>
<span class="badge">New</span>
</div>
<div class="hero rows" id="rows-long">
<img alt="Kurtas on a market stall" width="1600" height="600" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1600' height='600'%3E%3Crect width='1600' height='600' fill='%23345'/%3E%3C/svg%3E">
<div class="scrim"></div>
<p class="caption" lang="hi">दिवाली सेल: हाथ से बुने कुर्ते, साड़ियाँ और दुपट्टे, देश भर के कारीगरों से, हर ऑर्डर पर उपहार पैकिंग के साथ</p>
<span class="badge">New</span>
</div>
<script>
// The size of each hero and its photo, where the caption starts and ends (from the hero's top edge), and
// whether the caption covers the badge.
const round = (n) => Math.round(n * 100) / 100;
for (const hero of document.querySelectorAll('.hero')) {
const h = hero.getBoundingClientRect();
const img = hero.querySelector('img').getBoundingClientRect();
const cap = hero.querySelector('.caption').getBoundingClientRect();
const badge = hero.querySelector('.badge').getBoundingClientRect();
const covers = cap.top < badge.bottom && cap.bottom > badge.top && cap.left < badge.right && cap.right > badge.left;
console.log(`${hero.id}: hero ${round(h.width)} × ${round(h.height)}, photo ${round(img.width)} × ${round(img.height)}; caption from ${round(cap.top - h.top)} to ${round(cap.bottom - h.top)}${covers ? ', over the badge' : ''}`);
}
</script>
</body>
</html> | At 320 px | Height | Caption |
|---|---|---|
| Cell, short | 120 px | 56 to 120 px |
| Cell, long | 160 px | 0 to 160 px |
| Absolute, long | 120 px | −40 to 120 px |
| Rows, long | 200 px | 40 to 200 px |
The rows are the one-cell version with the short and the long caption, the absolutely positioned version and the two-row version, each with its banner height and where its caption ran, from the banner’s top edge. All three engines agreed, at 320 px and at 360 px. The long caption covered the badge in the one-cell and the absolute versions, and left it clear in the two-row version. At 360 px the long caption still fitted inside the photo, which is why this bug ships: it only shows on narrower screens, with longer translations or with larger text. WCAG 2.2 asks pages to work at 320 CSS pixels (success criterion 1.4.10, Reflow), and there the absolutely positioned caption ran 40 px above the photo, over whatever came before the banner, because an absolutely positioned box does not make its container any taller. In the grid cell the caption is an ordinary item, so the row grew to fit it and the photo, sized to fill the cell, grew too. The two-row version goes one step further: the photo and the scrim span both rows, the badge has the first row to itself and the caption the second, so they can never cover each other.
Overlapping items paint in the order of the HTML, later ones on top. To change that, give an item a z-index: grid
items accept it without position: relative, and an item with a z-index other than auto forms its own stacking
context. Keep the order of the HTML the order people should read in, whatever the stacking.
Grid lanes: masonry as an enhancement
A masonry layout fills columns with items of different heights, each item going wherever a column ends highest, with
no gaps under the short ones. CSS Grid Level 3 defines it as display: grid-lanes, after years of discussion under the
name masonry. Level 3 is still a Working Draft, and only Safari supports it (version 26.4 and later), so a page must
start from a layout every browser has and switch to lanes inside a feature query:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A photo wall with grid lanes and a grid fallback</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.wall {
display: grid; /* every browser: rows of tiles */
inline-size: 600px;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
gap: 12px;
align-items: start; /* tiles keep their own height */
}
@supports (display: grid-lanes) {
.wall { display: grid-lanes; } /* each tile goes into the lane that ends highest */
}
.wall > * { background: #dde; }
/* Stand-ins for photos of different shapes. */
.wall > :nth-child(1) { block-size: 120px; }
.wall > :nth-child(2) { block-size: 200px; }
.wall > :nth-child(3) { block-size: 160px; }
.wall > :nth-child(4) { block-size: 90px; }
.wall > :nth-child(5) { block-size: 240px; }
.wall > :nth-child(6) { block-size: 150px; }
.wall > :nth-child(7) { block-size: 180px; }
.wall > :nth-child(8) { block-size: 110px; }
</style>
</head>
<body>
<div class="wall">
<div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div>
</div>
<script>
// Which layout ran, then each tile's column and top edge, and the wall's height.
const wall = document.querySelector('.wall');
const box = wall.getBoundingClientRect();
const tiles = [...wall.children].map((t) => {
const r = t.getBoundingClientRect();
return `${t.textContent}: column ${Math.round((r.left - box.left) / 204) + 1}, top ${Math.round(r.top - box.top)}`;
});
console.log(`display: ${getComputedStyle(wall).display}; the wall is ${Math.round(box.height)}px tall`);
console.log(` ${tiles.join(' · ')}`);
</script>
</body>
</html> | Engine | Layout used | Wall height |
|---|---|---|
| Chromium 153 | the grid fallback | 644 px |
| Firefox 155 | the grid fallback | 644 px |
| WebKit 26.6 | grid lanes | 484 px |
In the fallback, tiles 1 to 8 started 0, 0, 0, 212, 212, 212, 464 and 464 px from the top; with lanes, 0, 0, 0, 132, 172, 212, 234 and 374 px. The fallback is an ordinary grid: every row is as tall as its tallest tile, so short tiles leave holes below them, and the wall is 160 px taller. With lanes, WebKit put tile 4 under tile 1 at 132 px, tile 5 under tile 3 at 172 px, and so on, always in the lane that ended highest. Notice that the top edges never go back up in the order of the HTML: lanes place items one after another, so reading and Tab order still run roughly from top to bottom, unlike dense packing.
Gap decorations
The lines between the cells of a dashboard used to need borders on the items, which double up where items meet and
disappear where a cell is empty. CSS Gap Decorations Level 1, a Working Draft, draws them in the gaps instead, with
column-rule (the property multi-column layout already had) and the new row-rule:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Lines in the gaps of a grid</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.dashboard {
display: grid;
inline-size: 600px;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 80px;
gap: 24px;
column-rule: 2px solid #c00; /* a line in every column gap */
row-rule: 2px solid #c00; /* a line in every row gap */
}
.dashboard > * { background: #dde; }
</style>
</head>
<body>
<div class="dashboard"><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div></div>
</body>
</html> To see whether the lines were drawn, each engine’s screenshot was sampled in the middle of a column gap and of a row gap. Chromium 153 drew both red lines; Firefox 155 and WebKit 26.6 left both gaps white, as if the rules were not there. Gap decorations work in Chromium 149 and later only, so use them for decoration that the layout does not depend on: browsers without them show the same grid with plain gaps.
Animating track sizes
Grid track lists can be animated: a transition from grid-template-columns: 16rem 1fr to 4rem 1fr narrows a
sidebar smoothly. This is Baseline widely available (Chrome 107, Firefox 66 and Safari 16 were the first to support
it).
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A sidebar that narrows with a transition</title>
<style>
body { margin: 0; font: 16px/24px Arial, sans-serif; }
.layout {
display: grid;
inline-size: 1000px;
grid-template-columns: 16rem 1fr;
transition: grid-template-columns 300ms linear;
}
.layout.collapsed { grid-template-columns: 4rem 1fr; }
@media (prefers-reduced-motion: reduce) {
.layout { transition: none; } /* jump straight to the new size */
}
.layout > * { background: #dde; }
</style>
</head>
<body>
<div class="layout"><nav>Menu</nav><main>Content</main></div>
<script>
// Narrow the sidebar, stop the transition halfway to read the columns, then let it finish.
const layout = document.querySelector('.layout');
getComputedStyle(layout).gridTemplateColumns;
layout.classList.add('collapsed');
const [transition] = layout.getAnimations();
if (transition) {
transition.pause();
transition.currentTime = 150;
console.log(`halfway: ${getComputedStyle(layout).gridTemplateColumns}`);
transition.finish();
} else {
console.log('no transition ran');
}
console.log(`at the end: ${getComputedStyle(layout).gridTemplateColumns}`);
</script>
</body>
</html> Halfway through the 300 ms linear transition, all three engines drew the sidebar 160 px wide, exactly halfway between
256 and 64 px. With reduced motion requested, as the operating system setting does, no transition ran at all and the
sidebar went straight to 64 px, which is what the prefers-reduced-motion rule asks for and what WCAG 2.2 success
criterion 2.3.3 recommends for motion that only decorates.
Practise: lay out an overlay card
Exercise · Medium · JavaScript
Position the layers of an overlay card
A card stacks a photo, a gradient scrim, a caption and a round button in one grid cell. The photo fills the cell and has a 3:2 shape, the scrim stretches over the whole cell, the caption is aligned to the bottom-left corner (align-self: end; justify-self: start) and the button to the top-right corner (align-self: start; justify-self: end). Write three functions in overlay.mjs and export them. All lengths are in px.
The first, alignInCell(space, size, value), returns { offset, size }: where an item of content size size starts in space px of room along one axis, and how big it is drawn, for the values start, center, end, stretch and safe center. stretch makes the item exactly as big as the room. center may give a negative offset when the item is bigger than the room; safe center then starts it at 0 instead.
The second, itemRect(cell, item, { justify, align }), returns { x, y, width, height } for an item in a cell, where cell and item are { width, height }: justify works across (x and width), align down (y and height).
The third, overlayCard(width, caption, button), returns { cell, scrim, caption, button } for a card width px wide: the cell's { width, height } and the three rectangles. The cell is as tall as the photo (width × 2 / 3) or as the caption, whichever is taller, because a grid row grows to fit its items. caption and button are their content sizes; when they are left out, the caption is 200 × 56 and the button 44 × 44.
The sample tests import the three functions from overlay.mjs and run in your browser. They accept answers within 0.01 px.
Starter code · overlay.mjs
/** Where an item of content size `size` starts in `space` px along one axis, and its drawn size. */
export function alignInCell(space, size, value) {
// Replace this line with your code.
return { offset: 0, size };
}
/** An item's rectangle in a cell: justify across, align down. */
export function itemRect(cell, item, { justify, align }) {
// Replace this line with your code.
return { x: 0, y: 0, width: item.width, height: item.height };
}
/** The cell and the scrim, caption and button rectangles of a card `width` px wide. */
export function overlayCard(width, caption = { width: 200, height: 56 }, button = { width: 44, height: 44 }) {
// Replace this line with your code.
const cell = { width, height: width };
return { cell, scrim: { x: 0, y: 0, ...cell }, caption: { x: 0, y: 0, ...caption }, button: { x: 0, y: 0, ...button } };
} The sample tests · overlay.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { alignInCell, itemRect, overlayCard } from './overlay.mjs';
const near = (actual, expected, what) => {
for (const key of Object.keys(expected)) {
assert.ok(Math.abs(actual[key] - expected[key]) < 0.01, `${what}: ${key} should be ${expected[key]}, got ${actual[key]}`);
}
};
test('one axis: start, center, end and stretch', () => {
near(alignInCell(200, 31.13, 'start'), { offset: 0, size: 31.13 }, 'start');
near(alignInCell(200, 31.13, 'center'), { offset: 84.435, size: 31.13 }, 'center');
near(alignInCell(200, 31.13, 'end'), { offset: 168.87, size: 31.13 }, 'end');
near(alignInCell(120, 24, 'stretch'), { offset: 0, size: 120 }, 'stretch');
});
test('centring an item bigger than its room, unsafe and safe', () => {
near(alignInCell(120, 200, 'center'), { offset: -40, size: 200 }, 'center');
near(alignInCell(120, 200, 'safe center'), { offset: 0, size: 200 }, 'safe center');
near(alignInCell(200, 100, 'safe center'), { offset: 50, size: 100 }, 'safe center with room');
});
test('both axes, as in the lesson', () => {
const cell = { width: 200, height: 120 };
const item = { width: 31.13, height: 24 };
near(itemRect(cell, item, { justify: 'center', align: 'center' }), { x: 84.435, y: 48, width: 31.13, height: 24 }, 'centre');
near(itemRect(cell, item, { justify: 'end', align: 'center' }), { x: 168.87, y: 48, width: 31.13, height: 24 }, 'end, centre');
near(itemRect(cell, item, { justify: 'stretch', align: 'stretch' }), { x: 0, y: 0, width: 200, height: 120 }, 'stretch');
});
test('the card at 360px', () => {
const card = overlayCard(360);
near(card.cell, { width: 360, height: 240 }, 'cell');
near(card.scrim, { x: 0, y: 0, width: 360, height: 240 }, 'scrim');
near(card.caption, { x: 0, y: 184, width: 200, height: 56 }, 'caption');
near(card.button, { x: 316, y: 0, width: 44, height: 44 }, 'button');
});
test('a caption taller than the photo makes the cell grow', () => {
const card = overlayCard(300, { width: 300, height: 220 });
near(card.cell, { width: 300, height: 220 }, 'cell');
near(card.scrim, { x: 0, y: 0, width: 300, height: 220 }, 'scrim');
near(card.caption, { x: 0, y: 0, width: 300, height: 220 }, 'caption');
near(card.button, { x: 256, y: 0, width: 44, height: 44 }, 'button');
}); A hint
Along one axis, the free room is space - size: start puts the item at 0, end at the free room, center at half of it. For the card, work out the cell first (Math.max(width * 2 / 3, caption.height) tall), then call itemRect for each layer with its alignment.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Interview questions
Fresher · What is the difference between justify-items and justify-content in a grid?
justify-items aligns each item inside its own grid area along the inline axis: start, center, end, or the
default normal, which stretches ordinary items over the whole cell. justify-self does the same for one item.
justify-content moves the tracks inside the grid container, and only matters when they leave free space: three 100
px columns in a 600 px grid start at 0, 100 and 200 px with start, at 150, 250 and 350 px with center, and at 0,
250 and 500 px with space-between. With fr columns there is no free space, so justify-content changes nothing,
while justify-items still moves items within their cells. The align- versions do the same down the page, and
place-items and place-content set both at once.
Key takeaways
-itemsand-selfalign items in their cells;-contentmoves tracks when they leave space over.- The default
normalstretches ordinary items but keeps images and other items with an aspect ratio at their size. place-items: centercentres a child in both directions; usesafe centerwhere an item can be taller than its row.- Put several items in one cell for overlays: the cell grows with its content, where an absolutely positioned caption runs out of its box; give badges and captions rows of their own when they must not cover each other.
- Grid items take
z-indexwithoutposition; keep the HTML in reading order whatever the stacking. display: grid-lanesworks only in Safari 26.4+: write an ordinary grid first, then switch inside@supports (display: grid-lanes).row-ruleandcolumn-rulein grids draw only in Chromium 149+; use them for decoration the layout does not need.- Track sizes animate in every current engine; turn the transition off under
prefers-reduced-motion: reduce.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- CSS Grid Layout Module Level 2: alignment and spacing (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: grid item sizing (W3C CSS Working Group)
- CSS Grid Layout Module Level 2: z-axis ordering (W3C CSS Working Group)
- CSS Box Alignment Module Level 3 (W3C CSS Working Group)
- CSS Box Alignment Module Level 3: overflow alignment, safe and unsafe (W3C CSS Working Group)
- CSS Grid Layout Module Level 3 (W3C CSS Working Group)
- CSS Gap Decorations Module Level 1 (W3C CSS Working Group)
- Web features explorer: Grid lanes (W3C WebDX Community Group)
- Web features explorer: Gap decorations (W3C WebDX Community Group)
- Web features explorer: Animatable grid tracks (W3C WebDX Community Group)
- Box alignment in grid layout (MDN Web Docs) (Mozilla)
- Understanding Success Criterion 1.4.10: Reflow (W3C Web Accessibility Initiative)
- Understanding Success Criterion 2.3.3: Animation from Interactions (W3C Web Accessibility Initiative)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress