Wrapping content around custom paths
For a long time, web designers have been forced to create within the constraints of the rectangle. Most content on the web is still trapped in simple boxes because most creative ventures into non-rectangular layout end in frustration. That is about to change with the introduction of CSS Shapes, available starting with Chrome 37. CSS Shapes allow web designers to wrap content around custom paths, like circles, ellipses and polygons, thus breaking free from the constraints of the rectangle.
Shapes can be defined manually or they can be inferred from images.
Let's look at a very simple example.
Perhaps you've been as naïve as me when first floating an image with transparent parts expecting the content to wrap and fill the gaps, only to be disappointed by the rectangular wrap shape persisting around the element. CSS Shapes can be used to solve this problem.
![Extracting a shape from an image](/static/articles/shapes-getting-started/image/extracting-shape-an-ima-35b58803a5775.jpg)
<img class="element" src="image.png" />
<p>Lorem ipsum…</p>
<style>
.element{
shape-outside: url(image.png);
shape-image-threshold: 0.5;
float: left;
}
</style>
The shape-outside: url(image.png)
CSS declaration tells the browser to extract a shape from the image.
The shape-image-threshold
property defines the minimum opacity level of pixels that will be used to create the shape. Its value must be in the range between 0.0
(fully transparent) and 1.0
(fully opaque). So, shape-image-threshold: 0.5
means that only pixels with opacity 50% and above will be used to create the shape.
The float
property is key here. While the shape-outside
property defines the shape of the area around which content will wrap, without the float, you won't see the effects of the shape.
Elements have a float area on the opposite side of their float
value. For example, if an element with a coffee cup image is being floated left, the float area will be created to the right of the cup. Even though you can engineer an image with gaps on both sides, content will only wrap around the shape on the opposite side designated by the float property, left or right, never both.
In the future, it will be possible to use shape-outside
on elements which are not floated with the introduction CSS Exclusions.
Creating shapes manually
Beyond extracting shapes from images, you can also code them manually. You can choose from a few functional values to create shapes: circle()
, ellipse()
, inset()
and polygon()
. Each shape function accepts a set of coordinates and it is paired with a reference box which establishes the coordinate system. More about reference boxes in a moment.
The circle() function
![Illustration of the circle() shape value](/static/articles/shapes-getting-started/image/illustration-the-circle-41300698b1e1e.png)
The full notation for a circle shape value is circle(r at cx cy)
where r
is the radius of the circle, while cx
and cy
are coordinates of the circle center on the X-axis and Y-axis. The coordinates for the circle center are optional. If you omit them, the center of the element (the intersection of its diagonals) will be used as the default.
.element{
shape-outside: circle(50%);
width: 300px;
height: 300px;
float: left;
}
In the example above, content will wrap around the outside of a circular path. The single argument 50%
specifies the radius of the circle, which, in this specific case, amounts to half of the element's width or height. Changing the dimensions of the element will influence the radius of the circle shape. This is a basic example of how CSS Shapes can be responsive.
Before going further, a quick aside: it is important to remember that CSS Shapes influence only the shape of the float area around an element. If the element has a background, that will not be clipped by the shape. To achieve that effect, you must use properties from CSS Masking - either clip-path or mask-image. The clip-path
property comes in very handy because it follows the same notation as CSS Shapes, so you can reuse values.
![Illustration of `circle()` shape + clip-path](/static/articles/shapes-getting-started/image/illustration-circle-557dc199e52fc.png)
Illustrations throughout this document use clipping to highlight the shape and help you understand the effects.
Back to the circle shape.
When using percentages for the circle radius, the value is actually computed with a slightly more complex formula: sqrt(width^2 + height^2) / sqrt(2). It's useful to understand this because it will help you imagine what the resulting circle shape will be if the element's dimensions are not equal.
All CSS unit types can be used in shape function coordinates - px, em, rem, vw, vh, and so forth. You can pick the one which is flexible or rigid enough for your needs.
You can adjust the circle's position by setting explicit values for the coordinates of its center.
.element{
shape-outside: circle(50% at 0 0);
}
This positions the circle center at the origin of the coordinate system. What is the coordinate system? This is where we introduce reference boxes.
Reference boxes for CSS Shapes
The reference box is a virtual box around the element, which establishes the coordinate system used to draw and position the shape. The origin of the coordinate system is at its upper-left corner with the X-axis pointing to the right and the Y-axis pointing down.
![Coordinate system for CSS Shapes](/static/articles/shapes-getting-started/image/coordinate-system-css-sh-3d9f3d6433162.png)
Remember that shape-outside
alters the shape of the float area around which content will wrap. The float area extends to the outer edges of the box defined by the margin
property. This is called the margin-box
and it is the default reference box for a shape if none is explicitly mentioned.
The following two CSS declarations have identical results:
.element{
shape-outside: circle(50% at 0 0);
/* identical to: */
shape-outside: circle(50% at 0 0) margin-box;
}
We have not yet set a margin on the element. At this point it is safe to assume that the origin of the coordinate system and the circle center are at the upper-left corner of the element's content area. This changes when you do set a margin:
.element{
shape-outside: circle(50% at 0 0) margin-box;
margin: 100px;
}
The origin of the coordinate system now lies outside the element's content area (100px up and 100px left), as does the circle center. The computed value of the circle radius also grows to account for the increased surface of the coordinate system established by the margin-box
reference box.
![Margin-box coordinate system with and without margin](/static/articles/shapes-getting-started/image/margin-box-coordinate-sys-e3bd8990048b4.png)
![Illustration of all reference boxes](/static/articles/shapes-getting-started/image/illustration-all-referen-7496cb776193a.png)
Only one reference box can be used at a given time with a shape-outside
declaration. Each reference box will influence the shape in a different and sometimes subtle way. There is another article which delves deeper and helps you understand reference boxes for CSS Shapes.
The ellipse() function
![Illustration of ellipse() shape value](/static/articles/shapes-getting-started/image/illustration-ellipse-s-846127e1dee7f.png)
Ellipses look like squished circles. They are defined as ellipse(rx ry at cx cy)
, where rx
and ry
are the radii for the ellipse on the X-axis and Y-axis, while cx
and cy
are the coordinates for the center of the ellipse.
.element{
shape-outside: ellipse(150px 300px at 50% 50%);
width: 300px;
height: 600px;
}
Percentage values will be computed from the dimensions of the coordinate system. No funny maths required here. You can omit the coordinates for the ellipse center and they will be inferred from the center of the coordinate system.
Radii on the X and Y axes may also be defined with keywords: farthest-side
yields a radius equal to the distance between the ellipse center and the side of the reference box farthest away from it, while closest-side
means the exact opposite - use the shortest distance between the center and a side.
.element{
shape-outside: ellipse(closest-side farthest-side at 50% 50%);
/* identical to: */
shape-outside: ellipse(150px 300px at 50% 50%);
width: 300px;
height: 600px;
}
This may come in handy when the element's dimensions (or the reference box) may change in unpredictable ways, but you want the ellipse shape to adapt.
The same farthest-side
and closest-side
keywords can also be used for the radius in the circle()
shape function.
The polygon() function
![Illustration of polygon() shape value](/static/articles/shapes-getting-started/image/illustration-polygon-s-b96636bce2fe8.jpg)
If circles and ellipses are too limiting, the polygon shape function opens up a world of options. The format is polygon(x1 y1, x2 y2, ...)
where you specify pairs of x y coordinates for each vertex (point) of a polygon. The minimum number of pairs to specify a polygon is three, a triangle.
.element{
shape-outside: polygon(0 0, 0 300px, 300px 600px);
width: 300px;
height: 600px;
}
Vertices are placed on the coordinate system. For responsive polygons you can use percentage values for some or all of the coordinates.
.element{
/* polygon responsive to font-size*/
shape-outside: polygon(0 0, 0 100%, 100% 100%);
width: 20em;
height: 40em;
}
There is an optional fill-rule
parameter, imported from SVG, which instructs the browser how to consider the “insideness” of a polygon in case of self-intersecting paths or enclosed shapes. Joni Trythall explains very well how the fill-rule property works in SVG. If not defined, the fill-rule
defaults to nonzero
.
.element{
shape-outside: polygon(0 0, 0 100%, 100% 100%);
/* identical to: */
shape-outside: polygon(nonzero, 0 0, 0 100%, 100% 100%);
}
The inset() function
The inset()
shape function allows you create rectangular shapes around which to wrap content. This might sound counter-intuitive considering the initial premise that CSS Shapes free web content from simple boxes. It may very well be. I have yet to find a use case for inset()
which isn't already achievable with floats and margins or with a polygon()
. Though inset()
does provide a more readable expression for rectangular shapes than polygon()
.
The full notation for an inset shape function is inset(top right bottom left border-radius)
. The first four position arguments are offsets inwards from the element's edges. The last argument is the border radius for the rectangular shape. It is optional, so you can leave it out. It follows the border-radius
shorthand notation you already use in CSS.
.element{
shape-outside: inset(100px 100px 100px 100px);
/* yields a rectangular shape which is 100px inset on all sides */
float: left;
}
Creating shapes from reference-boxes
If you don't specify a shape function to the shape-outside
property, you can allow the browser to derive a shape from the element's reference box. The default reference box is margin-box
. Nothing exotic so far, that's how floats already work. But applying this technique, you can reuse the geometry of an element. Let's look at the border-radius
property.
If you use it to round the corners of a floated element, you get the clipping effect, but the float area remains rectangular. Add shape-outside: border-box
to wrap around the contour created by the border-radius
.
![Extracting a shape from an element's border-radius using the border-box reference box](/static/articles/shapes-getting-started/image/extracting-shape-an-ele-5bf20ea5abb6d.png)
.element{
border-radius: 50%;
shape-outside: border-box;
float: left;
}
Of course, you can use all the reference boxes in this manner. Here's another use for derived shapes - offset pull-quotes.
![Creating an offset pull-quote using the content-box reference box](/static/articles/shapes-getting-started/image/creating-offset-pull-quo-6cfdf95608a9d.png)
It is possible to achieve the offset pull-quote effect by using only float and margin properties. But that requires you to position the quote element in the HTML tree at the point you want it to render.
Here's how to achieve the same offset pull-quote effect with added flexibility:
.pull-quote{
shape-outside: content-box;
margin-top: 200px;
float: left;
}
We explicitly set the content-box
reference box for the shape's coordinate system. In this case, the amount of content in the pull-quote defines the shape around which outside content will wrap. The margin-top
property is used here to position (offset) the pull-quote, regardless of its position in the HTML tree.
Shape margin
You'll notice that wrapping content around a shape can make it rub too closely against the element. You can add spacing around the shape with the shape-margin
property.
.element{
shape-outside: circle(40%);
shape-margin: 1em;
float: left;
}
The effect is similar to what you're used to from using the regular margin
property, but the shape-margin
affects only the space around shape-outside
value. It will add spacing around the shape only if there is room for it in the coordinate system. That's why in the example above the circle radius is set to 40%, not 50%. If the radius was set to 50%, the circle would have taken up all the space in the coordinate system leaving no room for the effect of shape-margin
. Remember, the shape is ultimately constrained to the element’s margin-box
(the element plus its surrounding margin
). If the shape is larger and overflows, it will be clipped to the margin-box
and you will end up with a rectangular shape.
It's important to understand that shape-margin
accepts only a single positive value. It does not have a long-hand notation. What is a shape-margin-top for a circle, anyway?
Animating shapes
You can mix CSS shapes with many other CSS features, such as transitions and animations. Though, I must stress that users find it very annoying when text layout changes while they're reading. Pay close attention to the experience if you do decide in favor of animating shapes.
You can animate the radii and centers for circle()
and ellipse()
shapes as long as they're defined in values that the browser can interpolate. Going from circle(30%)
to circle(50%)
is possible. However, animating between circle(closest-side)
to circle(farthest-side)
will choke the browser.
.element{
shape-outside: circle(30%);
transition: shape-outside 1s;
float: left;
}
.element:hover{
shape-outside: circle(50%);
}
![GIF of animated circle](/static/articles/shapes-getting-started/image/gif-animated-circle-f3bdb9d718fd8.gif)
More interesting effects can be achieved when animating polygon()
shapes, with the important note that the polygon must have the same number of vertices between the two animation states. The browser can't interpolate if you add or remove vertices.
One trick is to add the maximum amount of vertices you need and position them clustered together in the animation state where you want fewer perceived edges to the shape.
.element{
/* four vertices (looks like rectangle) */
shape-outside: polygon(0 0, 100% 0, 100% 100%, 0 100%);
transition: shape-outside 1s;
}
.element:hover{
/* four vertices, but second and third overlap (looks like triangle) */
shape-outside: polygon(0 0, 100% 50%, 100% 50%, 0 100%);
}
![GIF of animated triangle](/static/articles/shapes-getting-started/image/gif-animated-triangle-daa5ed7de7048.gif)
Wrapping content inside a shape
![Screenshot of Alice in Wonderland demo using CSS Shapes to wrap content](/static/articles/shapes-getting-started/image/screenshot-alice-wonder-62a3bd15c33c8.jpg)
The initial draft of the CSS Shapes specification included a shape-inside
property which allowed you to wrap content inside of a shape. There were even implementations in Chrome and Webkit for a while. But wrapping arbitrarily-positioned content inside a custom path requires much more effort and research to cover all possible scenarios and avoid bugs. That's why the shape-inside
property has been deferred to CSS Shapes Level 2 and implementations for it have been withdrawn.
However, with some effort and a bit of compromise, you can still achieve the effect of wrapping content inside a custom shape. The hack is to use two floated elements with shape-outside
, positioned at opposite sides of a container. The compromise is that you have to use one or two empty elements which have no semantic meaning, but serve as the struts to create the illusion of a shape inside.
<div>
<div class='left-shape'></div>
<div class='right-shape'></div>
Lorem ipsum...
</div>
The position of the .left-shape
and .right-shape
strut elements at the top of the container is important because they will be floated to the left and to right in order to flank the content.
.left-shape{
shape-outside: polygon(0 0, ...);
float: left;
width: 50%;
height: 100%;
}
.right-shape{
shape-outside: polygon(50% 0, ...);
float: right;
width: 50%;
height: 100%;
}
![Illustration of workaround for shape-inside for Alice demo](/static/articles/shapes-getting-started/image/illustration-workaround-074b19a04e4da.jpg)
This styling makes the two floated struts take up all the space within the element, but the shape-outside
properties carve out space for the rest of the content.
If CSS Shapes are not supported by the browser, this will yield ugly effects by pushing all of the content down. That's why it's important to use the feature in a progressively enhanced manner.
In the earlier shape animation examples, you'll notice that text shifting around can be bothersome. Not all use cases warrant an animated shape. But you can animate other properties which interact with CSS Shapes to add effect where it makes sense.
In the Alice in Wonderland demonstration of CSS Shapes, we used the scroll position to change the top margin of the content. The text is squeezed between two floated elements. As it moves down, it has to relayout according to the shape-outside
of the two floated elements. This gives the impression that the text is going down the rabbit hole and it adds to the storytelling experience. Borderline gratuitous? Maybe. But it does look cool.
Because the text layout is done natively by the browser, the performance is better than using a JavaScript solution. But changing the margin-top on scroll does trigger lots of relayout and paint events, and that may noticeably reduce performance. Use with caution! However, using CSS Shapes without animating them does not come with a perceivable performance hit.
Progressive enhancement
Start off by assuming that the browser does not have CSS Shapes support and build up on that when you do detect the feature. Modernizr is a good solution to do feature detection and there is a test for CSS Shapes in the 'Non-core detects' section.
Some browsers provide feature detection in CSS via the @supports
rule without the need for external libraries. Google Chrome, which also supports CSS Shapes, understands the @supports
rule. This is how you use it to progressively enhance:
.element{
/* styles for all browsers */
}
@supports (shape-outside: circle(50%)){
/* styles only for browsers which support CSS Shapes */
.element{
shape-outside: circle(50%);
}
}
Lea Verou has written more about how to use the CSS @supports rule.
Disambiguation from CSS Exclusions
What we know today as CSS Shapes used to be called CSS Exclusions and Shapes in the early days of the spec. The switch in naming might seem as a nuance, but it is actually very important. CSS Exclusions, now a separate spec, enables wrapping content around elements positioned arbitrarily, without the need for a float property. Imagine wrapping content around an absolutely-positioned element; that's a use case for CSS Exclusions. CSS Shapes just define the path around which the content will wrap.
So, shapes and exclusions are not the same thing, but they do complement each other. CSS Shapes are available in browsers today, while CSS Exclusions are not yet implemented with shapes interaction.
Tools for working with CSS Shapes
You can create paths in classic image authoring tools, but none of them, at the time of this writing, exports the required syntax for CSS Shapes values. Even if they did, working like that wouldn't be too practical.
CSS Shapes are meant to be used in the browser, where they react to other elements on the page. It's very useful to visualize the effects of editing the shape on the content which surrounds it. There are a few tools to help you with this workflow:
Brackets: The CSS Shapes Editor extension for Brackets uses the code editor's live preview mode to overlay an interactive editor for editing shape values.
Google Chrome: The CSS Shapes Editor extension for Google Chrome extends the browser's Developer Tools with controls to create and edit shapes. It places an interactive editor on top of the selected element.
The inspector in Google Chrome has built-in support for highlighting shapes. Hover over an element with a shape-outside
property and it will light up to illustrate the shape.
Shapes from images: If you prefer to generate images and have the browser extract shapes from them, Rebecca Hauck has written a good tutorial for Photoshop.
Polyfill: Google Chrome is the first major browser to ship CSS Shapes. There is upcoming support for the feature on Apple's iOS 8 and Safari 8. Other browser vendors may consider it in the future. Until then, there is a CSS Shapes polyfill to provide basic support.
Conclusion
In a web where content is mostly trapped in simple boxes, CSS Shapes provide a way to create expressive layout, bridging the fidelity gap between web and print design. Of course, shapes can be abused and create distractions. But, when applied with taste and good judgement, shapes can enhance the content presentation and focus the user’s attention in a way which is unique to them.
I leave you with a collection of work by others, mostly from print, which demonstrates interesting uses for non-rectagular layout. I hope this inspires you to try CSS Shapes and experiment with new design ideas.
Many thanks to Pearl Chen, Alan Stearns and Zoltan Horvath for reviewing this article and providing valuable insight.