Open any HTML file, add three <div> elements inside a container, and give each one a background colour. Without writing a single line of layout CSS, here is exactly what the browser does: it stacks them one below the other, each one taking the full width of its parent, tallest to shortest reading order. This is not a bug — it is called normal flow, and every block-level element (div, p, section, li) follows it by default. But almost no real interface actually looks like a vertical stack. A row of buttons at the bottom of a form, a set of product cards on a shopping page, a navigation bar across the top of a site, three stat numbers sitting side by side on a dashboard — all of these need elements sitting next to each other, correctly sized, with the leftover space distributed in a predictable way. For years, web developers forced this to happen using float, display: inline-block, and negative-margin tricks that broke the moment the content changed. Flexbox, formally the CSS Flexible Box Layout module, replaced all of it with one clean idea: you tell the browser "arrange these children along a line," and the browser does the spacing and sizing arithmetic for you. That arithmetic is exact and learnable — and that is what this chapter teaches.
Flex containers and flex items
Flexbox always involves two roles, and mixing them up is the single most common beginner error. A flex container is any element with display: flex (or display: inline-flex) set on it. A flex item is any element that is a direct child of a flex container — grandchildren are not automatically affected. Turning on flex changes nothing about how the container itself sits on the page; it only changes how the container arranges its own children.
Here is the toolbar example made concrete. First, without flexbox:
<div class="toolbar">
<div class="btn">Save</div>
<div class="btn">Cancel</div>
<div class="btn">Delete</div>
</div>
.btn {
width: 100px;
padding: 10px;
background: #2f6fed;
color: white;
}
Because .btn is a plain div, each one is block-level: three buttons, three separate rows, full width available to each even though each is only 100px wide (the box itself is 100px, but it still claims its own line). Now add one line to the parent:
.toolbar {
display: flex;
}
That single declaration turns .toolbar into a flex container. Immediately, its three .btn children stop following normal block flow and line up left-to-right in a row, each one shrinking to only the width it actually needs (100px, as set), sitting side by side. Nothing was changed on the buttons themselves — the parent's display: flex is what reprograms how the children are placed. This is the core mental model: flexbox is a property you set on the parent to control the children.
Two axes: the idea every flexbox property builds on
Once a container is flex, the browser thinks of it as having two perpendicular axes, and every flexbox property positions items relative to one of the two:
- The main axis is the direction items are laid out along. By default (
flex-direction: row), the main axis runs left to right. - The cross axis is always perpendicular to the main axis. For the default row direction, the cross axis runs top to bottom.
Set flex-direction: column on the container, and the two axes swap: the main axis now runs top to bottom, and the cross axis runs left to right. This single swap is the reason the same two alignment properties, justify-content and align-items, can arrange items either horizontally or vertically — they always describe main axis and cross axis, never "horizontal" and "vertical" directly. The diagram below shows both cases side by side, with the exact axis each property controls labelled.
Main-axis spacing: justify-content, with real arithmetic
justify-content controls how leftover space along the main axis gets distributed once all the items have taken the room they need. It only has anything to do once there is leftover space — if the items exactly fill the container, every value looks the same. Let us make it exact with numbers, because justify-content is really just arithmetic on one number: the leftover space.
Take a container 700px wide holding three items, each 100px wide:
.row {
display: flex;
justify-content: space-between; /* try each value below */
width: 700px;
}
.item { width: 100px; }
Total space used by the three items: 3 × 100px = 300px. Leftover space along the main axis: 700 − 300 = 400px. Every value of justify-content is a different rule for spending that 400px:
flex-start(the default): all 400px goes after the last item. Items are pushed to the left edge, touching each other's intended gaps not at all — they simply sit left-aligned.flex-end: all 400px goes before the first item. Items are pushed to the right edge.center: 400px is split evenly on both sides — 200px of empty space to the left of item 1, 200px to the right of item 3.space-between: no space at the outer edges at all; the 400px is split only between items. Three items have 2 gaps between them, so each internal gap is 400 ÷ 2 = 200px.space-around: each item gets equal space on both its sides. The 400px is divided by the number of items, 3, giving each item 400 ÷ 3 ≈ 133.3px of "personal space," split as 66.7px on each side. The visual effect: gaps between items are double the size of the gaps at the two edges (each internal gap = 66.7 + 66.7 = 133.3px, each edge gap = 66.7px).space-evenly: every gap, including the two edges, is made exactly equal. With 3 items there are 4 gaps (edge, gap, gap, edge), so 400 ÷ 4 = 100px everywhere.
Notice the pattern: space-between divides by (number of items − 1), space-evenly divides by (number of items + 1), and space-around divides by the number of items but then halves the result at the two edges. Memorising the visual result is fragile; memorising this arithmetic is not.
Cross-axis alignment: align-items
align-items does for the cross axis what justify-content does for the main axis, but with an important twist: its default value is not "leave items alone," it is stretch. Give a flex container an explicit height and leave its items without a height of their own, and by default every item stretches to fill that height completely:
.row {
display: flex;
height: 200px; /* align-items defaults to stretch */
}
.item { width: 100px; } /* no height set */
Here, all three .item boxes become exactly 200px tall, matching the container, even though no height was written on .item at all. This surprises many beginners who expect elements to only ever be as tall as their content. Setting align-items: flex-start, center, or flex-end switches off the stretching and instead packs each item to its natural content height, positioned at the top, vertical centre, or bottom of the cross axis respectively. There is also align-items: baseline, which lines up items by the baseline of their first line of text — useful when items hold text at different font sizes and you want the letters, not the boxes, to line up.
Wrapping: one line or many
By default, a flex container has flex-wrap: nowrap: every item is forced onto a single line no matter how many there are or how wide they want to be. Setting flex-wrap: wrap instead allows the browser to break items onto additional lines once a line runs out of room, moving to the next line the way words wrap in a paragraph.
A worked example makes the wrap point exact. Consider a photo gallery:
.gallery {
display: flex;
flex-wrap: wrap;
gap: 10px;
width: 650px;
}
.photo { width: 200px; height: 150px; }
Photo 1 takes 200px. Adding photo 2 needs a 10px gap plus 200px more: running total 410px. Adding photo 3: another 10 + 200 = 620px, still under 650px, so it fits. Adding photo 4 would need 10 + 200 = 210px more, taking the running total to 830px, which is 180px over the 650px container. So the browser wraps: exactly three photos sit on the first line, and photo 4 (and any further photos) start a fresh line beneath. This is deterministic, not a rendering guess — you can always find the wrap point with the same running-total addition.
Sharing space that is left over or missing: flex-grow and flex-shrink
So far, every item's width has been fixed. Flexbox's real power is letting items grow to absorb leftover space or shrink to fit into too little space, controlled by two properties that work on individual items (not the container):
flex-basis: the item's starting size along the main axis, before growing or shrinking is applied. If omitted, the browser uses the item's own width (orauto, which usually falls back to its content size).flex-grow: a number (default0) saying how eagerly this item should claim any leftover space, relative to its siblings.flex-shrink: a number (default1) saying how eagerly this item should give up space when there is not enough room, relative to its siblings.
These three are so often used together that CSS provides a shorthand: flex: grow shrink basis;. The extremely common flex: 1 is shorthand for flex: 1 1 0% — grow eagerly, shrink eagerly, start from zero.
Here is a full worked example of flex-grow arithmetic. A container is 900px wide, holding three items each with flex-basis: 200px:
.row { display: flex; width: 900px; }
.a { flex-basis: 200px; flex-grow: 1; }
.b { flex-basis: 200px; flex-grow: 1; }
.c { flex-basis: 200px; flex-grow: 2; }
Step 1: add up the basis widths — 200 + 200 + 200 = 600px. Step 2: find the leftover space — 900 − 600 = 300px. Step 3: add up the grow numbers — 1 + 1 + 2 = 4 "shares." Step 4: each share is worth 300 ÷ 4 = 75px. Step 5: hand out shares — item a gets 1 share (75px extra), item b gets 1 share (75px extra), item c gets 2 shares (150px extra). Final widths: a = 275px, b = 275px, c = 350px. Check: 275 + 275 + 350 = 900px, exactly filling the container.
A misconception worth fixing directly
Many students, after seeing wrap and grow, assume the reverse case — items that are too big to fit — must overflow the container, since nothing was said about shrinking them. This is wrong, and it is wrong in a specific, testable way. Because flex-shrink defaults to 1 on every flex item, items shrink automatically to stay inside a non-wrapping row, unless you explicitly turn shrinking off.
Take four cards, each given a fixed width of 200px, inside a 700px-wide container with flex-wrap: nowrap (the default) and no flex-shrink set:
.row { display: flex; flex-wrap: nowrap; width: 700px; }
.card { width: 200px; } /* flex-shrink defaults to 1 */
Natural total width: 4 × 200 = 800px. That is 100px more than the 700px container. Because flex-shrink defaults to 1 and all four cards are identical, the 100px overflow is shared equally: 100 ÷ 4 = 25px shrink each. Each card ends up 200 − 25 = 175px wide, and 4 × 175 = 700px — the row fits exactly, just narrower than the width you wrote in the CSS. Nothing overflows.
Overflow only happens if you explicitly disable shrinking, flex-shrink: 0, on those cards. With shrinking off, the browser is told "never make this item smaller than its basis," so the 800px of content is forced into the 700px box and 100px of it spills outside the container (visible as horizontal overflow, scrollable if overflow-x allows it). The rule to remember: fixed widths on flex items are starting sizes, not guarantees — flex-shrink: 1 is the quiet default that keeps rows from overflowing unless you turn it off yourself.
A second, related mix-up is worth naming too: students often assume justify-content always means "horizontal" and align-items always means "vertical." That is only true for the default flex-direction: row. Switch to flex-direction: column, and the axes swap: justify-content now controls vertical spacing (because the main axis runs top to bottom) and align-items controls horizontal alignment (because the cross axis now runs left to right). Always read these two properties as "along the main axis" and "along the cross axis," never as fixed compass directions, and the column case stops being confusing.
gap: spacing without margin hacks
Before gap was supported inside flexbox, developers added spacing between items using margin-right on every item except the last one — fragile, because forgetting the "except the last" rule leaves stray extra space at one edge. The gap property (also called row-gap and column-gap individually) is set once on the container and inserts space only between items, never at the outer edges:
.row {
display: flex;
gap: 16px;
}
With three items, gap: 16px inserts exactly two 16px gaps (between item 1–2 and item 2–3), and none at the far left or far right. This composes cleanly with justify-content and flex-wrap without any extra bookkeeping, which is why modern CSS prefers gap over margin tricks.
Putting it together: a train-search result row
A realistic example many Indian students will recognise: a row of train class cards on a ticket-booking results page, each showing a class name, seats left, and fare, that must sit in a neat row on a desktop screen and squeeze fairly on a smaller one.
<div class="class-row">
<div class="class-card">
<h4>3A</h4><p>12 seats • ₹1240</p>
</div>
<div class="class-card">
<h4>2A</h4><p>4 seats • ₹1850</p>
</div>
<div class="class-card">
<h4>SL</h4><p>WL 6 • ₹480</p>
</div>
</div>
.class-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: flex-start;
}
.class-card {
flex: 1 1 180px;
min-width: 150px;
border: 1px solid #ccc;
padding: 12px;
}
Reading the shorthand flex: 1 1 180px the way this chapter taught it: flex-basis is 180px (each card starts by asking for 180px), flex-grow is 1 (any leftover space on a wide screen is shared equally, so the three cards stretch to fill the row evenly), and flex-shrink is 1 (on a narrow screen, cards shrink together rather than overflowing). The min-width: 150px is a safety floor: shrinking will not push a card below 150px even if the shrink arithmetic alone would allow it, at which point flex-wrap: wrap takes over and moves a card to a new line instead of squeezing it further. This is a genuinely common production pattern: flex-basis for the ideal size, flex-grow to fill space, flex-shrink plus a min-width floor to control how small is too small, and flex-wrap as the final fallback.
Check your understanding
A flex container is 900px wide with
justify-content: space-evenlyand holds three items of 150px each. How wide is each gap, including the two edges?
Total item width: 3 × 150 = 450px. Leftover: 900 − 450 = 450px.space-evenlymakes all gaps equal, and with 3 items there are 4 gaps (edge, between, between, edge): 450 ÷ 4 = 112.5px per gap.A container has
height: 300pxandalign-itemsis left at its default. An item inside has no height set. How tall does the item render, and why?
300px, the full height of the container. The default value ofalign-itemsisstretch, which stretches items with no explicit cross-axis size to fill the container along the cross axis.Four cards, each
width: 200px, sit in aflex-wrap: nowrapcontainer that is 700px wide, with noflex-shrinkset on the cards. What actually happens, and how wide does each card end up?
They do not overflow.flex-shrinkdefaults to1, so the 100px of overflow (4 × 200 = 800px against a 700px container) is shared equally across the four identical cards: 100 ÷ 4 = 25px off each, leaving every card 175px wide (700 ÷ 4 = 175, matching). The cards would only overflow the container ifflex-shrink: 0had been set explicitly.You set
flex-direction: columnon a container and thenjustify-content: centeron it. What visibly moves, and why?
The items move vertically, toward the middle of the container's height. Settingflex-direction: columnmakes the main axis run top to bottom, andjustify-contentalways distributes space along the main axis — so with a column direction, it controls vertical position rather than horizontal.
Summary
display: flexon a parent turns its direct children into flex items arranged along a main axis;flex-direction(defaultrow) picks whether the main axis runs horizontally or vertically, and the cross axis is always perpendicular to it.justify-contentdistributes leftover space along the main axis: subtract total item size from container size to get the leftover, then apply the chosen value's rule (edges only forflex-start/flex-end, split evenly forcenter, between-only forspace-between, and so on).align-itemspositions items along the cross axis, defaulting tostretch(items fill the container's cross-axis size unless they have their own size set).flex-wrap: wraplets items overflow onto new lines instead of squeezing or spilling out of a single line; the defaultnowrapforces one line.flex-grow(default 0) distributes leftover main-axis space proportionally;flex-shrink(default 1) removes space proportionally when items do not fit — this default is why fixed-width items usually shrink to fit rather than overflow.flex-basissets an item's starting main-axis size before grow/shrink is applied;flex: grow shrink basisis the standard shorthand, andflex: 1meansflex: 1 1 0%.gapadds spacing strictly between items, with none at the outer edges, replacing older margin-based hacks.
Think About It
Think about this: How would you explain css flexbox 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 css flexbox 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 css flexbox to at least 3 other topics you have studied.