Wrapping Text to Fit Shaped Containers with CSS

With CSS, you can make all kinds of shapes, but if you try to put any text inside even a simple non-rectangular shape, you can run into problems. The text will not wrap to fit inside the container.

See the Pen raOVjY by Jonathan Skeate (@skeate) on CodePen.

However, recently I discovered the CSS property shape-outside. Browser support isn’t great (currently only Webkit-based browsers), but there is a polyfill. It allows you to wrap text around a floated element, as if it had a shape you specify. Perhaps the most common example is a circle:

See the Pen qEOdrM by Jonathan Skeate (@skeate) on CodePen.

Gorgeous. But how does this help with the original problem? This handles wrapping outside a shape. There’s no shape-inside (yet).

Until that arrives, we can still manage this. The trick lies in inverting the shape-outside. In other words, make the shape a polygon, with two points at (0,0) and (0, 100%), and the rest following the contour of the desired shape. It’s not perfect (for circles at least) but it’s good enough, since text would never be perfect anyway.

One caveat: this only works with floated elements. We can float the ::before pseudoelement just fine, but if we try to float the ::after, it doesn’t look right. See the example below — you can hover over the circle to color the pseudoelements, to make it more obvious what’s happening.

See the Pen gbarrN by Jonathan Skeate (@skeate) on CodePen.

Unfortunately the only way I’ve found around it so far is double-wrapping the circle element, so you can use both ::before elements. Forgiveable, I suppose, but I would like a single-element solution.

See the Pen YPyqWd by Jonathan Skeate (@skeate) on CodePen.

Here’s an SCSS mixin to handle it all:

@mixin circle($radius){
  width: $radius*2;
  height: $radius*2;
  border-radius: $radius;
  &::before{
    content: '';
    height: 100%;
    width: 50%;
    float: left;
    shape-outside: polygon(
      0 0, 100% 0, 60% 4%, 40% 10%, 20% 20%, 10% 28.2%, 5% 34.4%, 0 50%,
      5% 65.6%, 10% 71.8%, 20% 80%, 40% 90%, 60% 96%, 100% 100%, 0 100%
    );
  }
  > span::before{
    content: '';
    height: 100%;
    width: 50%;
    float: right;
    shape-outside: polygon(
      100% 0, 0 0, 40% 4%, 60% 10%, 80% 20%, 90% 28.2%, 95% 34.4%, 100% 50%,
      95% 65.6%, 90% 71.8%, 80% 80%, 60% 90%, 40% 96%, 0 100%, 100% 100%
    );
  }
}

comments powered by Disqus