Before 2015 or so, a question like "how do I vertically centre a box on the page?" was treated as a small joke in web development — everyone had run into it, and everyone had a slightly ugly workaround. Here is one of the classic tricks, using a CSS property meant for HTML tables, repurposed to centre a div:
.box {
display: table-cell;
width: 300px;
height: 200px;
vertical-align: middle;
text-align: center;
}
Here is another, using absolute positioning and a coordinate trick:
.box {
position: relative;
width: 300px;
height: 200px;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
Both work. Both are also fragile — the table-cell trick borrows a layout model designed for tabular data and quietly changes how the box behaves in other ways (for instance, width and height percentages start behaving like table cells, not blocks); the transform trick requires you to know the child's own size doesn't matter because translate(-50%, -50%) shifts it back by exactly half of its own width and height, which is easy to get backwards when you're new to it. Neither approach was designed for the job it's doing.
Flexbox was designed for exactly this job. The same centring, done with the layout model actually built for it:
.box {
display: flex;
justify-content: center;
align-items: center;
width: 300px;
height: 200px;
}
Three lines, and they say precisely what you mean: "lay these children out with flexible spacing, and centre them on both axes." That directness — describing the layout you want instead of tricking the browser into producing it — is the entire point of flexbox, and it's why nearly every modern website's navigation bars, card grids, form rows, and toolbars are built with it.
Turning a box into a flex container
Flexbox introduces exactly two roles. The element you apply display: flex to becomes a flex container. Every element that is a direct child of that container automatically becomes a flex item — you don't add a class to the children; the container declaration is enough.
.gallery {
display: flex;
}
If .gallery has five <img> children, all five instantly become flex items and immediately line up left-to-right, each keeping its natural width, with no wrapping. That single line already changed their layout behaviour — this is important to notice, because it means display: flex is not a passive "enable some features" switch; it actively takes over how the children are positioned, the moment it's applied. Grandchildren (an <img> nested two levels deep inside a <figure> inside .gallery, say) are untouched by this — only direct children become flex items, unless that grandchild's own parent is separately made into a flex container.
The main axis and the cross axis
Every flex container has two axes, and almost every point of confusion beginners have with flexbox traces back to mixing these two up. The main axis is the direction items are laid out along. The cross axis runs perpendicular to it. Which physical direction (horizontal or vertical) each one points in is controlled by one property: flex-direction.
.row {
display: flex;
flex-direction: row; /* the default — main axis is horizontal */
}
.column {
display: flex;
flex-direction: column; /* main axis is vertical */
}
With the default row, the main axis runs left to right (in English-language pages) and the cross axis runs top to bottom. Switch to column, and the main axis becomes top-to-bottom while the cross axis becomes left-to-right. Nothing about the items changed — only which direction counts as "main" flipped.
Common misconception: students who learn justify-content as "the property that spaces things out horizontally" get tripped up the moment someone writes flex-direction: column. It doesn't move horizontally any more — because the main axis is now vertical, justify-content now controls vertical spacing, and align-items (the cross-axis property) is the one that now controls horizontal position. The properties never change what they do — justify-content always controls the main axis and align-items always controls the cross axis — but which physical direction that means depends entirely on flex-direction. Read the axis, not the compass direction.
Spacing items along the main axis: justify-content
justify-content decides how leftover space along the main axis is distributed among the items. In a 900px-wide row container holding three 100px-wide items, there are 900 − 300 = 600px of leftover space to place somewhere, and each value places it differently:
.container {
display: flex;
justify-content: space-between;
}
flex-start(the default) — all 600px of leftover space goes after the last item; items huddle at the start.flex-end— the 600px goes before the first item; items huddle at the end.center— the 600px splits in half: 300px before the first item, 300px after the last.space-between— no space at the outer edges at all; the 600px splits into the two gaps between the three items, 300px each, so the first item touches the left edge and the last touches the right edge.space-around— each item gets 200px of space on both its left and right (600px ÷ 3 items); but because two neighbouring items each contribute 200px to the gap between them, the gaps between items end up looking twice as wide (400px) as the gaps at the outer edges (200px).space-evenly— the browser instead solves for a single gap size so that outer edges and inner gaps are all identical: with 3 items there are 4 gaps (before item 1, between 1–2, between 2–3, after item 3), so 600px ÷ 4 = 150px everywhere.
Aligning items on the cross axis: align-items
align-items controls the same items' position on the perpendicular axis:
.container {
display: flex;
align-items: center; /* also: flex-start, flex-end, stretch, baseline */
}
The default value is stretch — this is worth memorising because it surprises people constantly. If you don't set a height on flex items in a row container, they will silently stretch to match the tallest item in the row, filling the container's full cross-size, even though you never asked for that. flex-start/flex-end/center align items to the top, bottom, or vertical middle of the row without stretching them. baseline is specialised for text-heavy items of different font sizes — it lines up items by the baseline of their first line of text rather than by their box edges, which matters when, say, a large heading and a small caption sit side by side and you want their text to look aligned rather than their boxes.
Wrapping onto new lines: flex-wrap and gap
By default, flex items refuse to wrap — flex-wrap: nowrap is the initial value, and if the items are too wide to fit, the browser will try to shrink them (more on that soon) rather than move any to a second line. For a gallery of photos, that's usually the wrong behaviour; you want rows to fill up and then continue on the next line, the way text wraps.
.gallery {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.photo {
width: 140px;
height: 100px;
}
Picture a school website publishing photos from a Republic Day parade rehearsal — dozens of 140×100px thumbnails inside one .gallery container. With flex-wrap: wrap, as many photos as fit on one line sit there, and the moment the next photo would overflow the container's width, it drops to a new line instead of being squeezed. gap: 12px then places a consistent 12px of space between every adjacent photo, both between columns in a row and between rows themselves — one declaration replacing what used to require careful margin arithmetic on every single item.
Common misconception: gap only creates space between items — it never adds space at the outer edges of the container. Someone expecting gap: 12px to behave like padding, putting 12px between the first photo and the container's left edge, will be confused when that photo sits flush against it. If you want edge spacing too, that's what padding on the container is for — gap and padding solve two different, complementary problems.
Growing to fill leftover space: flex-grow
So far every item has kept a fixed width. flex-grow lets items expand to consume whatever space is left over after all items have taken their base width — called the flex-basis. This is where flexbox needs a genuine worked calculation, because the arithmetic is exactly what a CBSE exam question would ask you to trace.
.container {
display: flex;
width: 900px;
}
.item {
flex-basis: 200px;
}
.item:nth-child(1) { flex-grow: 1; }
.item:nth-child(2) { flex-grow: 2; }
.item:nth-child(3) { flex-grow: 1; }
Trace it step by step:
- Add up the basis widths. Three items, 200px each: 200 + 200 + 200 = 600px already claimed.
- Find the leftover space. Container is 900px wide, so 900 − 600 = 300px is still unclaimed.
- Add up the grow values to find the total "shares." 1 + 2 + 1 = 4 shares altogether, so each share is worth 300 ÷ 4 = 75px.
- Give each item its shares on top of its basis. Item 1 gets 1 share: 200 + 75 = 275px. Item 2 gets 2 shares: 200 + (2 × 75) = 200 + 150 = 350px. Item 3 gets 1 share: 200 + 75 = 275px.
- Check it sums back to the container width. 275 + 350 + 275 = 900px. Exactly accounted for.
Common misconception: flex-grow does not distribute the container's total width in the ratio you specify — it only distributes the leftover space after every item's basis has already been subtracted. An item with flex-grow: 2 does not automatically end up twice as wide as the container overall; it gets twice as large a share of the leftover 300px as an item with flex-grow: 1, which is a much smaller effect once the basis widths are large relative to the leftover space. It's also worth stating the default plainly: flex-grow: 0 is the initial value, so items do not grow at all unless you explicitly opt them in — a container with extra space and no flex-grow set anywhere simply leaves that space unclaimed after the last item.
Shrinking to fit tight space: flex-shrink
flex-shrink is the mirror problem: what happens when the items' basis widths add up to more than the container, rather than less?
.container {
display: flex;
width: 400px;
}
.item {
flex-basis: 200px;
flex-shrink: 1;
}
Three items with a 200px basis each want 600px total, but the container is only 400px — a deficit of 600 − 400 = 200px that has to be removed from somewhere. Because all three items share the same basis (200px) and the same shrink factor (1), the deficit divides evenly across all three: 200 ÷ 3 ≈ 66.67px comes off each item, leaving each one at 200 − 66.67 ≈ 133.33px. Check: 133.33 × 3 ≈ 400px, which fits the container exactly. (When items have different basis widths, the browser weights the shrink amount by each item's basis too, so a larger item gives up proportionally more — but for items that start out equal, as here, an equal three-way split is exactly correct, not an approximation.)
Because flex-grow, flex-shrink, and flex-basis are used together so often, CSS provides a shorthand:
.item {
flex: 1; /* shorthand for flex-grow: 1; flex-shrink: 1; flex-basis: 0% */
}
Note that flex: 1 sets the basis to 0%, not auto — this matters. With a basis of auto (the actual initial default for items that don't specify flex at all — the built-in starting point is flex: 0 1 auto), each item first claims space based on its own content size, and only leftover space beyond that gets distributed by grow ratios. With flex: 1, the basis starts at zero, so the entire container width is treated as "leftover" and split purely by the grow ratio, making every flex: 1 item come out equally wide regardless of how much content it holds. This is precisely why flex: 1 on every item is the standard trick for "N equal-width columns."
Reordering items without touching the HTML: order
Every flex item has an order property with a default value of 0. The browser renders items sorted by this value, smallest first, and items with equal order keep their original HTML order relative to each other.
.nav { display: flex; }
.logo { order: 0; } /* default, could be omitted */
.links { order: 1; }
.cta { order: -1; } /* renders first, even though it's last in the HTML */
If the HTML markup lists .logo, then .links, then .cta in that source order, the visual result is .cta first (order −1 is the smallest value), then .logo (order 0), then .links (order 1) — purely a CSS reshuffle. This is genuinely useful, not a party trick: a mobile navbar might want its "Book Now" button visually first for thumb reach, while the underlying document (and, importantly, the order screen readers and keyboard-tab navigation follow) stays logically unchanged. That last point is also a caution — visual order and tab order can now disagree, so order should be used to fine-tune layout, not to fully rewrite a page's structure.
Overriding one item's alignment: align-self
align-items sets the cross-axis alignment for every item in the container at once, but a single item can opt out:
.item.featured {
align-self: flex-end;
}
If the container has align-items: center, every item sits vertically centred except .featured, which ignores that and pins itself to the bottom of the cross axis instead. align-self accepts the same values as align-items (flex-start, flex-end, center, stretch, baseline) — it's simply align-items scoped to one item rather than the whole container.
Putting it together: a responsive navbar
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding: 12px 24px;
}
Suppose .navbar holds exactly two children: a .logo and a .links group. justify-content: space-between pushes the logo to the far left and the links group to the far right, with all remaining horizontal space collapsing into the single gap between them. align-items: center vertically centres both, so a taller logo image and a shorter line of links text still line up on their vertical middles rather than their tops. On a wide screen this reads as a single tidy row. Shrink the browser window enough that logo width + links width can no longer both fit, and flex-wrap: wrap takes over: the links group drops to a second line rather than getting squeezed illegibly, and gap: 8px keeps 8px of breathing room between the two lines exactly as it would between columns. Nothing about the CSS changes between the wide and narrow cases — the same three properties (justify-content, flex-wrap, gap) simply produce different, still-correct layouts at different widths, which is the essence of why flexbox is the default tool for responsive component layout.
CBSE-style question: explain the output
.box {
display: flex;
width: 300px;
height: 150px;
flex-direction: column;
justify-content: center;
align-items: flex-end;
}
/* .box contains three .item children, each 40px tall */
Question: Describe where the three .item children render inside .box, and explain why.
Answer: Because flex-direction: column makes the main axis vertical, justify-content: center centres the group of three stacked items vertically within the 150px height — not horizontally, even though "justify" sounds horizontal. The three items together are 120px tall (3 × 40px), leaving 30px of leftover space, split 15px above and 15px below by centering. Meanwhile align-items: flex-end governs the cross axis, which in column mode is horizontal — so all three items are pushed to the right edge of the 300px-wide box, rather than stretched or centred horizontally. The result is a vertical stack of three items, vertically centred as a block, hugging the right edge. Getting this right requires tracking that both properties' meanings are unchanged, but their visible direction rotated together with flex-direction.
Check your understanding
1. A flex container is 600px wide. It has two items, each with flex-basis: 100px. The first item has flex-grow: 3, the second has flex-grow: 1. What are their final widths?
Answer: Basis total = 100 + 100 = 200px. Leftover = 600 − 200 = 400px. Grow shares = 3 + 1 = 4, so 1 share = 100px. Item 1 gets 3 shares: 100 + 300 = 400px. Item 2 gets 1 share: 100 + 100 = 200px. Check: 400 + 200 = 600px. ✓
2. A flex container is 500px wide with justify-content: flex-start (the default). It holds three items, each with flex-basis: 150px and no flex-grow or flex-shrink set. Will the items grow, shrink, or stay at 150px, and where does any leftover space go?
Answer: Basis total = 3 × 150 = 450px, which is less than the 500px container, so there's 50px of leftover space — but flex-grow defaults to 0, and no item opted in, so nothing grows to absorb it. The items stay at exactly 150px each. With the default justify-content: flex-start, that unclaimed 50px simply sits after the third item, at the end of the row.
Summary
display: flexturns an element into a flex container; its direct children become flex items automatically.flex-directionchooses which physical direction is the main axis (row= horizontal,column= vertical); the cross axis is always perpendicular to it.justify-contentalways controls main-axis spacing;align-itemsalways controls cross-axis alignment — both rotate together whenflex-directionchanges.flex-wrap: wraplets items flow onto new lines instead of being forced to fit or shrink;gapspaces items apart without adding space at the container's outer edges.flex-growdistributes only the leftover space after every item'sflex-basisis subtracted, split by grow-value ratio; it defaults to0, so nothing grows unless told to.flex-shrinkremoves space proportionally when items' total basis exceeds the container, weighted by each item's basis (equal basis items shrink by equal amounts).flex: 1is shorthand forflex-grow: 1; flex-shrink: 1; flex-basis: 0%, and is the standard way to make several items equally wide regardless of content.orderre-sequences the visual rendering order of items (default0) without touching HTML source order or, by itself, the accessibility tab order.align-selfoverridesalign-itemsfor a single item.
Think About It
Think about this: How would you explain flexbox: flexible box layout explained to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where flexbox: flexible box layout explained is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting flexbox: flexible box layout explained to at least 3 other topics you have studied.