CSS Flexbox Complete Guide — Every Property Explained (With Real-World Examples)

I still remember the day I nearly threw my laptop out the window. It was 2014, and I was trying to center a div vertically. If you’ve been in web development long enough, you know the pain — position: absolute with negative margins, table-cell hacks, and JavaScript-based solutions that broke on resize.

Then Flexbox arrived.

The first time I used display: flex and align-items: center, I actually laughed out loud. Three properties. That’s all it took to solve a problem that had haunted developers for years. Since then, I’ve used Flexbox in nearly every project — from simple marketing sites at my agency job to complex dashboard interfaces at my current role.

But here’s the thing: most tutorials only cover the basics. They show you how to make a pretty navigation bar and call it a day.

What is Flexbox? The Mental Model

Before we touch a single line of code, you need to understand how Flexbox thinks.

Flexbox (Flexible Box Layout) is a one-dimensional layout model. That “one-dimensional” part is crucial — it means Flexbox handles either rows or columns, but not both at the same time (that’s what CSS Grid is for).

Think of Flexbox as a relationship between two things:

  1. The Flex Container (the parent) — This is the element with display: flex or display: inline-flex
  2. The Flex Items (the children) — These are the direct children of the container

Every property in Flexbox falls into one of two categories: properties for the container, and properties for the items. Let’s break them all down.

Part 1: Flex Container Properties

display: flex vs display: inline-flex

.container {
  display: flex; /* Takes full width, stacks children horizontally */
}

.container-inline {
  display: inline-flex; /* Shrinks to content width, still flex behavior */
}

The difference? display: flex makes the container a block-level element. display: inline-flex makes it behave like an inline element while maintaining flex behavior for its children.

Real-world use case: I used inline-flex last week for a series of tags in a blog post. The tags sat next to each other in a paragraph without breaking the text flow.

flex-direction — Setting the Main Axis

.container {
  flex-direction: row; /* Default: left to right */
  flex-direction: row-reverse; /* Right to left */
  flex-direction: column; /* Top to bottom */
  flex-direction: column-reverse; /* Bottom to top */
}

This property defines the main axis — the primary direction your items will stack. The cross-axis is perpendicular to it.

Pro Tip: I always think of flex-direction as asking “Should these items read like a sentence (row) or like a list (column)?”

flex-wrap — Handling Overflow

.container {
  flex-wrap: nowrap; /* Default: items will shrink to fit */
  flex-wrap: wrap; /* Items will wrap to new lines */
  flex-wrap: wrap-reverse; /* Wraps in reverse order */
}

Without flex-wrap, Items will try to squeeze onto one line (and shrink if necessary). With flex-wrap: wrap, they’ll maintain their size and create new rows/columns.

flex-flow — The Shorthand

.container {
  flex-flow: row wrap; /* flex-direction | flex-wrap */
}

I rarely use this shorthand because I find it harder to read at a glance, but it’s good to know it exists.

justify-content — Main Axis Alignment

This is probably the property you’ll use most often. It controls alignment along the main axis.

.container {
  justify-content: flex-start; /* Default: items at start */
  justify-content: flex-end; /* Items at end */
  justify-content: center; /* Items centered */
  justify-content: space-between; /* Equal space between items, none at ends */
  justify-content: space-around; /* Equal space around each item */
  justify-content: space-evenly; /* Equal space between all items and edges */
}

Real-world example: In a pricing card component I built for a SaaS client, I used justify-content: space-between a feature list container to push the CTA button to the bottom while keeping features stacked at the top.

align-items — Cross Axis Alignment (Single Line)

While justify-content it works on the main axis, align-items works on the cross axis.

.container {
  align-items: stretch; /* Default: items fill container height */
  align-items: flex-start; /* Items align to top */
  align-items: flex-end; /* Items align to bottom */
  align-items: center; /* Items center vertically */
  align-items: baseline; /* Items align by text baseline */
}

The baseline Value is subtle but powerful. When you have items with different font sizes, baseline ensures their text aligns properly — perfect for navigation menus with mixed heading sizes.

align-content — Cross Axis Alignment (Multiple Lines)

Here’s where beginners get confused. align-content only works when you have multiple lines of flex items (when flex-wrap: wrap is active and items actually wrap).

.container {
  align-content: flex-start; /* Lines packed at top */
  align-content: flex-end; /* Lines packed at bottom */
  align-content: center; /* Lines centered */
  align-content: space-between; /* Equal space between lines */
  align-content: space-around; /* Equal space around each line */
  align-content: stretch; /* Default: lines stretch to fill space */
}

If you’re trying to space out wrapped rows of items, align-content is your friend — not align-items.

Part 2: Flex Item Properties

Now, let’s look at properties you apply to the children themselves.

order — Changing Visual Order

.item-1 {
  order: 2;
}

.item-2 {
  order: 1; /* This appears first */
}

.item-3 {
  order: 3;
}

By default, all items have order: 0. Items are placed from lowest order value to highest. Negative values are allowed.

Accessibility Warning: Changing order only changes visual order, not source order. Screen readers still follow the HTML structure. Use sparingly and only for visual enhancements, not logical content structure.

flex-grow — The Hunger Games

.item {
  flex-grow: 0; /* Default: don't grow */
  flex-grow: 1; /* Take up available space */
  flex-grow: 2; /* Take up twice as much available space as items with 1 */
}

flex-grow determines how items distribute positive free space (extra space beyond their content). Think of it as a ratio.

If one item has flex-grow: 2 and another has flex-grow: 1, the first gets twice as much of the available extra space.

flex-shrink — When Things Get Tight

.item {
  flex-shrink: 1; /* Default: allow shrinking */
  flex-shrink: 0; /* Prevent shrinking */
}

While flex-grow handles extra space, flex-shrink handles negative free space — when items would overflow. The default value of 1 means items can shrink proportionally.

Real-world example: In a card grid for an e-commerce site, I set flex-shrink: 0 on product images so they never get squished, while allowing the text descriptions to shrink if needed.

flex-basis — The Starting Point

.item {
  flex-basis: auto; /* Default: based on content width */
  flex-basis: 200px; /* Start at 200px */
  flex-basis: 25%; /* Start at 25% of container */
}

flex-basis sets the initial size of an item before flex-grow and flex-shrink do their work. It overrides any width property when flex-direction is row.

flex — The Shorthand You’ll Actually Use

.item {
  flex: 0 1 auto; /* Default: don't grow, can shrink, auto basis */
  flex: 1; /* flex: 1 1 0 — the classic "take remaining space" */
  flex: 2; /* flex: 2 1 0 — take twice as much space */
  flex: 0 0 200px; /* flex: 0 0 200px — fixed width, no growth/shrink */
}

The flex shorthand is flex-grow flex-shrink flex-basis. I use this constantly. The single-value flex: 1 is probably my most-used CSS property.

align-self — Individual Rebellion

.item {
  align-self: auto; /* Default: inherit from container */
  align-self: flex-start;
  align-self: flex-end;
  align-self: center;
  align-self: stretch;
  align-self: baseline;
}

This overrides the container’s align-items for a specific item. Need one item in a row to sit at the bottom while others stay at the top? align-self: flex-end is your answer.

This classic layout has been solved beautifully by Flexbox:

.holy-grail {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.holy-grail header,
.holy-grail footer {
  flex: 0 0 80px; /* Fixed height, no growing/shrinking */
  background: #333;
  color: white;
}

.holy-grail .content {
  display: flex;
  flex: 1; /* Takes remaining space */
}

.holy-grail nav {
  flex: 0 0 200px;
  background: #f0f0f0;
}

.holy-grail main {
  flex: 1; /* Takes available middle space */
  padding: 20px;
}

.holy-grail aside {
  flex: 0 0 200px;
  background: #f0f0f0;
}

Why this works: The outer container fills the viewport. The content area takes all remaining space with flex: 1, and inside it, the main content grows while sidebars stay fixed.

Real-World Example 2: Responsive Card Grid

I built this for a photography portfolio site that needed to handle images of varying heights:

.card-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20px; /* Modern browsers: use gap for consistent spacing */
  margin: 0 auto;
}

.card {
  flex: 1 1 300px; /* Grow, shrink, base 300px */
  display: flex;
  flex-direction: column;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.card-image {
  flex: 0 0 200px; /* Fixed height image area */
  background-size: cover;
  background-position: center;
}

.card-content {
  flex: 1; /* Takes remaining space */
  padding: 20px;
  display: flex;
  flex-direction: column;
}

.card-content p {
  flex: 1; /* Pushes button to bottom */
}

.card-button {
  flex: 0 0 auto;
  padding: 10px;
  background: #0066cc;
  color: white;
  text-align: center;
  border-radius: 4px;
}

The magic: Each card grows/shrinks to fit the grid while maintaining a minimum width of 300px. Inside each card, the description pushes the button to the bottom, regardless of text length.

Real-World Example 3: Form Layout with Perfect Alignment

Forms are notoriously tricky. Here’s how I handle them with Flexbox:

.form-row {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

.form-row label {
  flex: 0 0 150px; /* Fixed width labels */
  text-align: right;
  padding-right: 15px;
  font-weight: 500;
}

.form-row input,
.form-row select,
.form-row textarea {
  flex: 1 1 auto; /* Take remaining space */
  min-width: 0; /* Prevents overflow issues */
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.form-row button {
  flex: 0 0 auto;
  margin-left: 150px; /* Aligns with inputs, not labels */
}

/* Mobile adaptation */
@media (max-width: 600px) {
  .form-row {
    flex-wrap: wrap;
  }

  .form-row label {
    flex: 0 0 100%;
    text-align: left;
    margin-bottom: 5px;
  }

  .form-row button {
    margin-left: 0;
  }
}

The result: Labels and inputs stay perfectly aligned on desktop, then stack gracefully on mobile.

Comparison Table: Flexbox vs. CSS Grid

Since Flexbox and Grid often get confused, here’s when to use each:

Aspect CSS Flexbox CSS Grid
Dimensionality One-dimensional (rows OR columns) Two-dimensional (rows AND columns)
Best For Navigation bars, card layouts, centering items, form controls Full page layouts, magazine-style designs, galleries
Content vs Layout Content-first — items size based on content Layout-first — define grid, then place items
Browser Support Excellent (IE11+) Very good (modern browsers)
Item Sizing Items share space proportionally Items placed in defined cells
Gap Property Yes (gaprow-gapcolumn-gap) Yes (gaprow-gapcolumn-gap)
Source Order Independence Limited (order property) Excellent (grid placement)

My Rule of Thumb: Use Flexbox for components, Grid for layouts. Or as Rachel Andrew (CSS Grid expert) puts it: “Grid for layout, Flexbox for components.”

Common Pitfalls and How to Avoid Them

After teaching Flexbox to hundreds of developers, these are the most common mistakes I see:

1. Forgetting min-width: 0 on Text-Containing Items

/* Problem */
.text-container {
  flex: 1;
  white-space: nowrap; /* This will overflow! */
}

/* Solution */
.text-container {
  flex: 1;
  min-width: 0; /* Allows text to wrap/truncate */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Flex items have a default min-width: auto, which prevents them from shrinking below their content size. Setting min-width: 0 overrides this.

2. Using justify-content and align-items Wrong

Remember:

  • justify-content = main axis
  • align-items = cross axis

Draw a little arrow on your whiteboard if you have to. I still do sometimes!

3. Not Testing on Different Screen Sizes

Flexbox behaves differently based on content. Always test with:

  • Very little content (one word)
  • A lot of content (paragraphs)
  • Different viewport sizes

4. Expecting gap in Legacy Browsers

The gap Property in Flexbox has good support (95%+ globally), but if you need to support older browsers, use margins with careful calculations.

Browser Support and Resources

Flexbox is well-supported across all modern browsers:

  • Chrome: 21+
  • Firefox: 28+
  • Safari: 6.1+
  • Edge: 12+
  • IE: 11 (with some bugs)

For deep dives, I recommend these resources:

Flexbox Changes How You Think About Layout

Looking back at that 2014 moment when I wanted to throw my laptop, I realize now that Flexbox didn’t just solve my layout problems — it changed how I think about CSS entirely.

Instead of fighting against the browser’s default behavior, Flexbox works with it. Instead of hacky calculations, we have logical properties that describe exactly what we want. Instead of rigid layouts, we have flexibility (pun intended).

Leave a Comment