CSS float: wrap text around images, and the fixes that come with it

float moves an element to the left or right edge and lets text flow around it. That is its real job today. Page layout belongs to flexbox and grid.

float moves an element to the left or right edge of its container, and the text after it wraps around it.

Use float: left or float: right on an image inside a paragraph, add a margin on the side that faces the text, and you get a newspaper-style picture in a column of text.

Try it. Switch the side and drag the margin slider.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Float an image in a paragraph</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; margin-bottom: 10px; font-size: 14px; }
  .controls button { font: inherit; padding: 5px 10px; border: 1px solid #c9cdd4; border-radius: 6px; background: #fff; cursor: pointer; }
  .controls button[aria-pressed="true"] { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  .controls label { display: flex; align-items: center; gap: 6px; }
  code { display: block; margin-bottom: 10px; padding: 6px 10px; border-radius: 6px; background: #1d2330; color: #d6f2df; font-size: 13px; }
  .text { margin: 0; padding: 14px; border-radius: 10px; background: #fff; font-size: 15px; line-height: 1.55; }

  /* the part that matters */
  .pic { float: left; margin: 0 16px 8px 0; width: 120px; height: 90px; border-radius: 8px; }
</style>
</head>
<body>
<div class="controls">
  <span>float:</span>
  <button data-f="left" aria-pressed="true">left</button>
  <button data-f="right" aria-pressed="false">right</button>
  <button data-f="none" aria-pressed="false">none</button>
  <label>margin <input id="m" type="range" min="0" max="32" step="4" value="16"> <span id="mv">16px</span></label>
</div>
<code id="css">.pic { float: left; margin: 0 16px 8px 0; }</code>

<p class="text">
  <svg class="pic" id="pic" viewBox="0 0 120 90" role="img" aria-label="Hills and sun">
    <rect width="120" height="90" fill="#cfe8ff"/>
    <circle cx="92" cy="24" r="12" fill="#fbbf24"/>
    <path d="M0 90 L0 62 Q30 36 60 60 T120 54 L120 90 Z" fill="#34a36b"/>
  </svg>
  A floated image leaves the normal line of content and moves to the left or right edge of its container.
  The lines of text that follow shorten themselves to fit beside it, then return to full width once they pass its bottom edge.
  That is the job float was designed for: pictures inside a column of text, the way a newspaper sets them.
  Switch to none and the image drops back into the line like a large letter, with the first line of text sitting next to its bottom.
</p>

<script>
  const pic = document.getElementById('pic');
  const m = document.getElementById('m');
  let side = 'left';

  function update() {
    const g = m.value + 'px';
    // keep the gap on the side that faces the text
    const margin = side === 'right' ? `0 0 8px ${g}` : side === 'left' ? `0 ${g} 8px 0` : '0';
    pic.style.float = side;
    pic.style.margin = margin;
    document.getElementById('mv').textContent = g;
    document.getElementById('css').textContent = `.pic { float: ${side}; margin: ${margin}; }`;
  }

  document.querySelectorAll('[data-f]').forEach((b) => {
    b.addEventListener('click', () => {
      side = b.dataset.f;
      document.querySelectorAll('[data-f]').forEach((x) => x.setAttribute('aria-pressed', x === b));
      update();
    });
  });
  m.addEventListener('input', update);
</script>
</body>
</html>
An inline SVG floated inside a paragraph. The code line shows the CSS for the current setting.
img.pic {
  float: left;
  width: 120px;
  margin: 0 16px 8px 0; /* gap on the right, where the text is */
}

What float actually does

A floated element leaves the normal flow of the page and moves sideways until it touches the edge of its container, or another float. Then the content after it lays out around it.

The detail that explains most float surprises: only the lines of text move aside. The boxes that hold them do not.

Left: a block's background runs under the float and only its text shortens. Right: a flow-root block narrows and sits beside it.
Left: a block's background runs under the float and only its text shortens. Right: a flow-root block narrows and sits beside it.

A paragraph after a float still starts at the container's left edge, and its background runs underneath the image. If that block needs to sit beside the float as a whole, give it display: flow-root and it becomes narrower instead of sliding under.

Float and margins

Margins on a float push text away from it, so put the margin on the side that faces the text: margin-right for float: left, margin-left for float: right. A small bottom margin keeps the first full-width line from touching the image.

Two other rules are useful to know:

  • Float margins never collapse. A float's top margin is added in full, and it does not merge with the margin of the element above, as two stacked paragraphs would.
  • A float becomes a block. A floated <span> or <img> computes to display: block, so width, height and vertical margins all apply.

Give floated images a max-width: 100% as well. A float wider than its container sticks out of it.

clear: start below the float

Sometimes the next thing must not wrap. A new heading next to the previous section's picture looks like a mistake. clear moves an element down until it is below earlier floats.

Value Moves the element below
clear: left Earlier float: left elements
clear: right Earlier float: right elements
clear: both Floats on either side
clear: none Nothing, the default
h2 { clear: both; } /* every section starts under the previous picture */

The collapsed parent problem

This is the classic "float not working" report. Put only floated children in a box, and the box's height becomes zero. Its border shrinks to a line at the top, its background disappears, and the next section slides up beside the floats.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Collapsed parent and three fixes</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
  .controls button { font: 13px system-ui, sans-serif; padding: 6px 10px; border: 1px solid #c9cdd4; border-radius: 6px; background: #fff; cursor: pointer; }
  .controls button[aria-pressed="true"] { background: #1d4ed8; border-color: #1d4ed8; color: #fff; }
  .readout { font-size: 14px; margin: 0 0 10px; }
  .readout b { color: #9a3412; }
  .readout b.ok { color: #0f5132; }

  .box { border: 3px solid #1d4ed8; border-radius: 10px; background: #e8efff; padding: 0 10px; }
  .card { float: left; width: 110px; margin: 10px 10px 10px 0; padding: 10px; border-radius: 8px; background: #fff; box-shadow: 0 3px 10px rgba(0, 0, 0, .12); font-size: 13px; }
  .card.tall { height: 110px; }
  .next { margin-top: 12px; padding: 10px 12px; border-radius: 8px; background: #fff3a8; font-size: 14px; }

  /* the three fixes: each one makes .box wrap its floats */
  .box.flow-root { display: flow-root; }
  .box.overflow  { overflow: auto; }
  .box.clearfix::after { content: ""; display: table; clear: both; }
</style>
</head>
<body>
<div class="controls">
  <button data-fix="" aria-pressed="true">no fix</button>
  <button data-fix="flow-root" aria-pressed="false">display: flow-root</button>
  <button data-fix="overflow" aria-pressed="false">overflow: auto</button>
  <button data-fix="clearfix" aria-pressed="false">clearfix ::after</button>
</div>
<p class="readout">Blue box height: <b id="h">0px</b></p>

<div class="box" id="box">
  <div class="card">Floated card</div>
  <div class="card tall">Taller floated card</div>
</div>
<div class="next">Next section. Without a fix it slides up and its text wraps around the cards.</div>

<script>
  const box = document.getElementById('box');
  const h = document.getElementById('h');

  function show() {
    const px = box.clientHeight;  // height inside the border
    h.textContent = Math.round(px) + 'px';
    h.className = px > 0 ? 'ok' : '';
  }

  document.querySelectorAll('[data-fix]').forEach((b) => {
    b.addEventListener('click', () => {
      box.className = 'box ' + b.dataset.fix;
      document.querySelectorAll('[data-fix]').forEach((x) => x.setAttribute('aria-pressed', x === b));
      show();
    });
  });
  show();
</script>
</body>
</html>
Two floated cards in a blue box. Try each fix and watch the height readout.
A float does not count toward its parent's height until the parent is told to wrap it.
A float does not count toward its parent's height until the parent is told to wrap it.

Three fixes, oldest last:

  1. display: flow-root on the parent. It exists for exactly this, and it has no side effects.
  2. overflow: auto on the parent. It works too, but anything that spills out of the box, such as a shadow or a dropdown, is clipped or gets a scrollbar.
  3. The clearfix. An empty ::after with clear: both at the end of the parent. You will still see it in older stylesheets. CSS ::before and ::after shows how the pseudo-element is built.
.box { display: flow-root; }

/* older code, same result */
.box::after { content: ""; display: table; clear: both; }

Wrap text around a circle with shape-outside

A round picture made with border-radius: 50% still looks square to the text. The lines stop at the edge of its box and the corners stay empty. shape-outside changes the shape the text flows around.

border-radius changes how the float is drawn. shape-outside changes where the text stops.
border-radius changes how the float is drawn. shape-outside changes where the text stops.
.round {
  float: right;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  shape-outside: circle(50%);
  shape-margin: 10px;
}

It has two requirements. The element must be floated, since shape-outside does nothing on a non-floated element. And it needs a real width and height, because circle(50%) is measured from the element's box. ellipse(), polygon() and an image's transparent areas work as shapes too.

Why float is no longer used for page layout

For years, sidebars and card rows were built by floating boxes, then clearing them and fixing their parents. Flexbox and grid were designed for layout and replace that work: equal heights, vertical centring and reordering come built in.

Job Use
Text wrapping around a picture or quote float
A row of buttons, a navbar, a card row Flexbox
Rows and columns at once, a page grid CSS grid

The two do not mix inside one container. Once the parent is display: flex or display: grid, float on its children is ignored. CSS display explains how the parent's display value decides how children are laid out.

A finished example: a magazine page

This page puts it together. A round picture floats right with shape-outside, the first letter is a drop cap made with a floated ::first-letter, and a pull quote floats left. The footer line uses clear: both.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Magazine layout with float and shape-outside</title>
<style>
  body { margin: 0; padding: 18px; font-family: Georgia, "Times New Roman", serif; background: #faf7f2; color: #2b2622; }
  article { max-width: 640px; margin: 0 auto; }
  .kicker { font: 700 12px system-ui, sans-serif; letter-spacing: .12em; text-transform: uppercase; color: #b45309; }
  h1 { margin: 4px 0 14px; font-size: 28px; line-height: 1.15; }
  p { margin: 0 0 12px; font-size: 16px; line-height: 1.6; }

  /* round picture: float it, then let the text follow the circle */
  .round {
    float: right;
    width: 190px; height: 190px;
    margin: 0 0 8px 14px;
    border-radius: 50%;
    shape-outside: circle(50%);  /* text follows the circle, not the square box */
    shape-margin: 10px;          /* gap between the circle and the text */
  }

  /* drop cap: the first letter floats left and spans about three lines */
  .lead::first-letter {
    float: left;
    font-size: 64px; line-height: .85;
    margin: 6px 8px 0 0;
    color: #b45309; font-weight: 700;
  }

  .quote {
    float: left; width: 42%;
    margin: 4px 16px 8px 0; padding: 10px 0 10px 12px;
    border-left: 4px solid #b45309;
    font-size: 19px; line-height: 1.35; font-style: italic;
  }
  .end { clear: both; padding-top: 10px; border-top: 1px solid #e3dcd2; font: 13px system-ui, sans-serif; color: #6b6259; }

  /* phones: too narrow to share a line, so stack instead */
  @media (max-width: 480px) {
    .round { float: none; display: block; margin: 0 auto 14px; width: 150px; height: 150px; shape-outside: none; }
    .quote { float: none; width: auto; margin: 0 0 12px; }
  }
</style>
</head>
<body>
<article>
  <div class="kicker">Field notes</div>
  <h1>A week on the coast, one tide at a time</h1>

  <svg class="round" viewBox="0 0 190 190" role="img" aria-label="Sunset over the sea">
    <defs><linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#fca5a5"/><stop offset=".55" stop-color="#fcd34d"/><stop offset=".55" stop-color="#0e7490"/><stop offset="1" stop-color="#164e63"/></linearGradient><clipPath id="disc"><circle cx="95" cy="95" r="95"/></clipPath></defs>
    <g clip-path="url(#disc)">
    <circle cx="95" cy="95" r="95" fill="url(#sky)"/>
    <circle cx="95" cy="104" r="26" fill="#fff7ed" opacity=".9"/>
    <rect x="0" y="104" width="190" height="86" fill="#0e7490"/>
    <path d="M30 128 H90 M110 146 H170 M50 164 H130" stroke="#67e8f9" stroke-width="3" stroke-linecap="round"/>
    </g>
  </svg>

  <p class="lead">Mornings start with the tide table taped inside the door. Low water decides everything: when the rock pools open, when the path round the point is safe, and when the café by the harbour fills up with people in wet boots.</p>

  <p>By the third day the rhythm is easy to read. The sea pulls back, the beach doubles in width, and for two hours the whole bay belongs to anyone willing to walk out across the ripples in the sand.</p>

  <div class="quote">"Low water decides everything."</div>

  <p>Afternoons are slower. The light turns gold early, the wind drops, and the boats come back in a loose line that nobody seems to organise. It is the best time to sit on the wall and do nothing at all.</p>

  <p>On the last evening the tide comes in fast over the flats, filling the channels first and then the gaps between them, until the whole bay is one sheet of water again.</p>

  <div class="end">Resize the window or open this on a phone: below 480px the picture and the quote stop floating and stack.</div>
</article>
</body>
</html>
On a wide screen the text curves around the picture. Below 480px, the picture and quote stop floating and stack.

On a phone, a 190px picture beside the text leaves a column only a few words wide. The page switches floats off in a media query:

@media (max-width: 480px) {
  .round { float: none; display: block; margin: 0 auto 14px; shape-outside: none; }
  .quote { float: none; width: auto; }
}

When it does not work

What you see Cause Fix
The parent's border or background disappears All its children are floated, so its height is 0 display: flow-root on the parent
The next heading wraps around the old picture The float is taller than its section clear: both on the heading
float does nothing The parent is display: flex or grid Use the parent's alignment, or unwrap it
float does nothing on a positioned box position: absolute or fixed turns float off Pick one: position it or float it
shape-outside has no effect The element is not floated, or has no size Add float and a width and height
Text is a thin strip beside the image on a phone The float takes most of the width float: none in a media query
The image sticks out of the text column The float is wider than its container max-width: 100% on the float

Text wrap only shows up at a real width. A screenshot freezes one screen size, and an .html attachment may open as plain code on a phone.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can open it on their own phone and see where the floats switch off. If you change the code later, the same link shows the new version.

Questions people ask

What does float: left do in CSS?

It takes the element out of the normal flow and moves it to the left edge of its container. Lines of text that come after it shorten so they sit to its right, and return to full width once they pass its bottom.

What does clear: both do?

It moves an element down until its top edge is below every earlier float, on the left and on the right. clear: left and clear: right only wait for floats on that one side.

Why is my parent element's height zero when its children are floated?

Floats do not count toward the height of a normal parent. Add display: flow-root to the parent and it grows to wrap them. overflow: auto and the older clearfix do the same.

Why is float not working on my element?

Check the parent first. Children of a display: flex or display: grid container are laid out by that container, and float has no effect on them. float also has no effect on an element with position: absolute or fixed.

Should I use float or flexbox for a layout?

Use flexbox for a row or column of items and grid for rows and columns together. Keep float for its original job: letting text wrap around an image or a pull quote.

Keep reading