CSS Flexbox: Interactive Guide
Flexbox is easier to internalize by dragging it than by reading about it. This guide covers the model, then hands you a live container and six boxes: change the container controls or drag a box onto another to swap their positions, and the CSS driving what you're looking at regenerates underneath in real time.
The Flexbox Model
Setting display: flex on a container turns its direct children
into flex items laid out along a main axis,
horizontal by default. Everything flexbox does is described in terms of that
main axis and the cross axis running perpendicular to it; the
five properties below all just position items along one or the other.
flex-direction is what decides which axis is which: in
row the main axis runs left to right and the cross axis runs top
to bottom; switch to column and that flips, the main axis becomes
vertical. Every other property below keeps the same meaning either way, since
they're defined relative to the main/cross axes, not literally to "horizontal"
and "vertical."
Live Demo
Container Properties
These five properties (plus display: flex itself) all live on the
container, not the items, and correspond directly to the
dropdowns in the demo above:
| Property | What it controls |
|---|---|
| flex-direction | Which way the main axis runs: row, row-reverse, column, or column-reverse. The -reverse variants flip the visual order without touching the underlying source order or the order property. |
| justify-content | Spacing of items along the main axis: bunched at the start or end, centered, or spread out with space-between (no space at the outer edges), space-around (equal space around each item, so edge gaps are half-size), or space-evenly (every gap, including the edges, is the same size). |
| align-items | Positioning of items along the cross axis. stretch (the default) makes every item fill the cross-axis size unless it has an explicit height/width set on that axis, which is exactly why the boxes in the demo only fully stretch vertically when flex-direction is a row. |
| flex-wrap | nowrap (the default) forces every item onto one line, shrinking them if they don't fit. wrap lets items overflow onto additional lines instead. |
| gap | Fixed spacing between items, on both axes. Modern and the preferred approach; the older alternative was margin tricks on individual items, which got messy fast once wrapping was involved. |
Try switching flex-direction to column in the demo,
then changing align-items: instead of moving boxes up and down,
it now moves them left and right, because the cross axis rotated along with
the main axis.
Item Properties: order, flex, and align-self
The container properties in Section 3 apply to every item at once. A handful
of properties go the other way: set on an individual item, they override how
that one item behaves within the container's layout. order is the
one driving the drag-and-drop demo above.
.box-3 { order: -1; /* visually first, regardless of where it sits in the HTML */ }
Every flex item has a default order of 0. Items are
laid out by sorting on order first, and falling back to source
(HTML) order for any items that tie, which is why two untouched boxes (both
still at the default 0) stay in their original relative order even
after a third box elsewhere gets pulled to the front with a negative value.
Critically, order changes only the visual position; it
doesn't move anything in the DOM, doesn't change tab order for keyboard
navigation, and doesn't change how a screen reader announces the content, which
makes it purely a visual layout tool and a poor substitute for actually
reordering meaningful content in the markup.
Often written together as the flex shorthand, these three control
how an item's size responds to the container having extra or insufficient
space. flex-basis is the item's starting size before any
growing/shrinking happens; flex-grow is a ratio describing how
much of any leftover space the item should claim relative to its
siblings; flex-shrink is the same idea in reverse, how much of a
size deficit the item should absorb when there isn't enough room for
every item at its basis size. flex: 1, by far the most common use,
is shorthand for flex: 1 1 0%: ignore each item's natural content
size and divide the container's space evenly between them.
align-self overrides the container's align-items for
one specific item on the cross axis only, useful for pulling a single item to
the opposite end of the cross axis from all its siblings without having to
restructure the container.
How the Drag-and-Drop Demo Works
The demo doesn't use the HTML5 native drag-and-drop API
(draggable="true", dragstart/drop),
which has no built-in touch support. It's built on the Pointer Events API
instead (pointerdown/pointermove/pointerup),
the same approach used in this site's
Pong guide,
so dragging works identically with a mouse, a finger, or a pen.
// On press, lift the box visually and remember where the pointer // grabbed it relative to the box's own top-left corner. box.addEventListener("pointerdown", function (e) { dragging = box; box.setPointerCapture(e.pointerId); var rect = box.getBoundingClientRect(); grabOffsetX = e.clientX - rect.left; grabOffsetY = e.clientY - rect.top; box.classList.add("is-dragging"); });
While the pointer moves, the dragged box follows it with a CSS
transform: translate(...), computed from the pointer's current
position minus that initial grab offset, so the box doesn't visually "jump" to
re-center on the cursor the instant the drag starts.
Finding what's underneath the dragged box is the part that needs a small trick:
the box being dragged is itself the topmost element at the pointer's position,
so a naive hit test would just find itself. Temporarily setting
pointer-events: none on the dragged box makes the browser see
straight through it to whatever box is actually underneath:
box.style.pointerEvents = "none"; var under = document.elementFromPoint(e.clientX, e.clientY); box.style.pointerEvents = ""; var target = under ? under.closest(".flex-box") : null;
On release, if the pointer is over a different box, the two boxes'
order values are swapped and the dragged box's transform is reset
to none; flexbox itself handles animating both boxes into their new slots,
since order is included in the box's transition-eligible
layout. Drop it back where it started, or off the container entirely, and
nothing changes; only a drop onto another valid box commits a swap.
Reference
| Property | Applies to | Default |
|---|---|---|
| display: flex | Container | — |
| flex-direction | Container | row |
| justify-content | Container | flex-start |
| align-items | Container | stretch |
| flex-wrap | Container | nowrap |
| gap | Container | 0 |
| order | Item | 0 |
| flex-grow | Item | 0 |
| flex-shrink | Item | 1 |
| flex-basis | Item | auto |
| align-self | Item | auto (inherits align-items) |