A List Apart

Menu
Issue № 394

CSS Shapes 101

by Published in Code, CSS · 51 Comments

Rectangles inside other rectangles: this is what our webpages have always been made of. We’ve long tried to break free from their restrictions by using CSS to create geometric shapes, but those shapes have never affected the content inside the shaped element, or how the element is seen by other elements on the page.

The new CSS Shapes specification is changing that. Introduced by Adobe in mid-2012, its goal is to provide web designers with a new way to change how the content flows inside and around arbitrarily complex shapes—something we’ve never been able to do before, not even with JavaScript.

For example, notice how the text flows around the circular images in the following design. Without Shapes, the text would be rectangular—throwing off the sophisticated touch that takes the design to the next level.

Image of a magazine layout with text wrapped around circular shaped images.
Notice how the text wraps around the circular shape of a bowl in this example. Using CSS Shapes, we can create similar designs for the web.

Let’s walk through how Shapes work, and how you can start using them.

Browser support

CSS Shapes are currently supported only in Webkit Nightly and Chrome Canary, but its Module Level 1 has reached the Candidate Recommendation status, so the properties and syntax defined in the spec are pretty stable. It won’t be long before they’re implemented in other browsers. This level focuses on the Shapes properties that change the flow of content around a shape. More specifically, it focuses on the shape-outside property and its related properties.

Combined with other cutting-edge features such as Clipping and Masking, CSS Filters, and Compositing and Blending, CSS Shapes will allow us to create more polished and sophisticated designs without having to resort to graphics editors like Photoshop or InDesign.

Future levels of CSS Shapes will focus on wrapping content inside a shape as well. For example, today it’s easy to create a rhombic shape in CSS: just rotate the element by 45 degrees, and then rotate the content inside it back so that it lies horizontally on the page. But the content inside the rhombus won’t be affected by the rhombic shape of its container, and will always remain rectangular. When the CSS Shapes shape-inside property is implemented, we will be able to make the content also become rhombic, making layouts like the one shown in the following image very much possible.

Image of a magazine layout using rhombic shapes.
Soon, CSS Shapes will also allow text inside shapes, like these rhombuses, to adjust to the edges of its container, rather than overflow or get cut off.

In order to use CSS Shapes in Chrome Canary today, you have to enable the experimental features flag. If you’re not sure how to do that, have a look at this walkthrough on the Adobe blog.

Creating a CSS Shape

You can apply a shape to an element using one of the Shapes properties. You pass the shape property a shape function as a value. The shape function is where you pass in arguments that define the shape that you want to apply to the element.

Illustration showing different parts making up a shape rule.

Shapes can be created using one of the following functions:

  • circle()
  • ellipse()
  • inset()
  • polygon()

Each shape is defined by a set of points. Some functions take points as parameters; others take offsets—but they all eventually draw the shapes as a set of points on the element. We’ll cover the parameters for each of these functions in the examples we’re going to create.

A shape can also be defined by extracting it from an image with an alpha channel. When an image is passed to a shape property, the browser then extracts the shape from the image based on a shape-image-threshold. The shape is defined by the pixels whose alpha value is greater than the threshold. The image must be CORS compatible. If the image provided cannot be displayed for any reason (such as if it doesn’t exist), then no shape will be applied.

The shape properties that accept the above functions as values are:

  • shape-outside: Wraps content around (outside) a shape
  • shape-inside: Wraps content inside a shape

You can use the shape-outside property in conjunction with the shape-margin property to add a margin around the shape that pushes the flowing content away from the shape, creating more room between it and the content. Just like shape-outside gets a shape-margin property, shape-inside gets a shape-padding property, which adds inner padding.

Using the shape properties and functions, declaring a shape on an element can be as easy as adding one line of CSS to it:

.element {
	shape-outside: circle(); /* content will flow around the circle defined on the element */
}

Or:

.element {
	shape-outside: url(path/to/image-with-shape.png);
}

But…

If you apply that line of CSS to your element, the shape won’t be applied to it unless two conditions are met:

  1. The element must be floated. Future levels of CSS Shapes may allow us to define shapes on non-floated elements, but not yet.
  2. The element must have intrinsic dimensions. The height and width set on an element will be used to establish a coordinate system on that element.

As you have seen in the function definitions above, the shapes are defined by sets of points. Because these points have coordinates, a coordinate system is necessary for the browser to know where to position each point on the element. So, the above example would work if it included something like:

.element {
	float: left;
	height: 10em;
	width: 15em;
	shape-outside: circle();
}

Giving an element intrinsic dimensions does not, however, affect its responsiveness (we’ll talk more about that later).

Since each shape is defined by a set of points positioned using a pair of coordinates, changing the coordinates of a point will directly affect the shape created. For example, the following image shows a hexagonal shape that can be created using the polygon() function. The shape is made up of six points. Changing the horizontal coordinate of the point in orange will change the resulting shape, and will affect the flow of content inside and/or outside any element this shape is applied to.

Image showing the result of changing the coordinate of a point on the created shape.
If the element is floated to the right and has this shape applied to it, the content on its left will change its flow when the coordinate of the orange point is changed inside the polygon() function.

A Shape’s reference box

CSS Shapes are defined and created inside a reference box, which is the box used to draw the shape on the element.  In addition to the element’s height and width, the element’s box model boxes—margin box, content box, padding box, and border box—are also used as a reference to specify the extent of the shape on an element.

By default, the margin box is used as a reference—so, if an element you want to apply a shape to has a margin at the bottom, the shape you define on the element will extend to the edges of the margin area, not the element’s border area. If you want to use one of the other box values, you can specify it in conjunction with the shape function that you pass to the shape properties:

shape-outside: circle(250px at 50% 50%) padding-box;

The padding-box keyword in the above rule specifies that the shape is going to be applied and restricted to the padding box (padding area) of the element. The circle() function defines a circular shape, its size, and position on the element.

Defining Shapes using shape functions

We’ll start by wrapping some information around a circular user avatar picture, like you might use to display a user profile or a testimonial.

Screenshot showing a user profile picture with text flowing around its circular boundaries.
Using CSS Shapes, text around a circular user profile image wraps to the shape, instead of staying rectangular.

We’ll use the circle() function to apply a circular shape to the profile image, using the following markup:

<img src="http://api.randomuser.me/0.3.2/portraits/men/7.jpg" alt="profile image" />

<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Harum itaque nam blanditiis eveniet enim eligendi quae adipisci?</p>

<p>Assumenda blanditiis voluptas tempore porro quibusdam beatae deleniti quod asperiores sapiente dolorem error! Quo nam quasi soluta reprehenderit laudantium optio ipsam ducimus consequatur enim fuga quibusdam mollitia nesciunt modi.</p>

You might ask, “Why not use border-radius to round the image?” The answer is that the border-radius property has no effect on the flow of the content inside or around the element it’s applied to—it does not affect the content area of the element, or the flow area of the content around the element. It only affects the borders of an element, and any backgrounds. The background area is clipped to the curved corners created by the property, but that’s it. The content inside the element will remain rectangular, and the content outside the element will still see and treat the element as if it were rectangular—because it is.

We are going to use the border-radius property to make the image circular—this is what we usually do to round images or other elements on a page:

img {
	float: left;
	width: 150px;
	height: 150px;
	border-radius: 50%;
	margin-right: 15px;
}
Screenshot showing how text would normally flow around an image without applying a CSS Shape to it.
Without CSS Shapes, the text sees the image as rectangular, and hence wraps around a rectangular shape, not a circular one.

In a browser that does not support CSS Shapes, the content around the circular image will flow around it as if it weren’t circular. This will be how the fallback will look in older browsers.

In order to change the flow of content to accommodate a certain shape, you use the shape properties.

img {
	float: left;
	width: 150px;
	height: 150px;
	border-radius: 50%;

	shape-outside: circle();
	shape-margin: 15px;
}

With this code, the text will “see” the circular shape applied to the image and wrap around it, as shown in the first screenshot. (You can see the result live here.) In non-supporting browsers it will fall back to the result shown in the second image.

You can use the circle() function as is, or by passing parameters to it. Its official syntax is:

circle() = circle( [<shape-radius>]? [at <position>]? )

The question marks indicate that these parameters are optional and can be omitted. If you omit one, that parameter is set to its default value by the browser. When you use circle() as is instead of explicitly setting its position, it will default to defining a circle that is positioned at the center of the element you’re applying it to.

You can specify the radius of the circle using any length unit (px, em, pt, etc.). You can also specify the radius using either closest-side or furthest-side, but closest-side is the default, which means the browser will use the length from the center of the element to its closest side as the length of the radius. farthest-side uses the length from the center to the farthest side.

shape-outside: circle(farthest-side at 25% 25%); /* defines a circle whose radius is half the length of the longest side, positioned at the point of coordinates 25% 25% on the element’s coordinate system*/

shape-inside: circle(250px at 500px 300px); /* defines a circle whose center is positioned at 500px horizontally and 300px vertically, with a radius of 250px */
Illustration showing a visual explanation of the closest-side and farthest-side values.

The ellipse() function works the same way as the circle() function, with the same list of values, except that instead of taking one radius parameter, it takes two: one for the length of the radius on the x-axis, and another for the y-axis.

ellipse() = ellipse( [<shape-radius>{2}]? [at <position>]? )

While not directly related to a circle or an ellipse, the inset() function is used to create rectangular shapes inside an element. Since elements are already rectangular, though, we don’t need it to make more rectangles. Instead, inset() can help us create rectangles with rounded corners that also have content flow around those corners.

Image showing text flow around an element with and without the inset() function applied to it.

The inset() function takes one to four offset values, which specify the offsets from the edges of the references box inward. These specify where the inset rectangle goes inside the element. The function also takes an optional parameter to round the corners of the inset rectangle. The rounded corners are specified exactly the same way as border-radius, using one to four values, in conjunction with the keyword round.

inset() = inset( offset{1,4} [round <border-radius>]? )

The following will create a rounded rectangle on a floated element.

.element {
	float: left;
	width: 250px;
	height: 150px;
	shape-outside: inset(0px round 100px) border-box;
}

Check the live example out here.

The last Shape function is the polygon(), which defines more complex arbitrary shapes using any number of points. The function takes in a set of coordinate pairs, each pair specifying the position of a point. The set of points make up the shape.

In the following example, an image floated to the right takes up the entire height of the screen using viewport units. We want to flow the text on the left around the hourglass inside the image, so we use the polygon() function to define an irregular shape on the image.

Screenshot showing the result of applying the polygon() function to define a shape on an image and have content wrap around that shape.

The CSS for the above image looks like this:

img.right {
	float: right;
	height: 100vh;
	width: calc(100vh + 100vh/4);
	shape-outside: polygon(40% 0, 100% 0, 100% 100%, 40% 100%, 45% 60%, 45% 40%);
}

You can set the coordinates of the points defining the shape in length units or percentages, as I have here.

This code alone will produce the the above result, but as you can see, the shape function does not affect the remaining parts of the image outside the defined shape. As a matter of fact, applying a shape function to an element—whether an image, a container, or something in between—will not affect anything except the content flow area. Backgrounds, borders, and everything else remain unchanged.

In order to visualize the shape of the polygon we created, we need to “clip out” the parts of the image outside the shape. This is where the clip-path property from the CSS Masking Module can help.

The clip-path property takes the same shape functions and values as the shape properties. If we pass the same polygonal shape we used in the shape-outside property to the clip-path property, it will clip all the parts of the image that are outside the defined shape.

img.right {
	float: right;
	height: 100vh;
	width: calc(100vh + 100vh/4);
	shape-outside: polygon(40% 0, 100% 0, 100% 100%, 40% 100%, 45% 60%, 45% 40%);
	/* clip the image to the defined shape */
	clip-path: polygon(40% 0, 100% 0, 100% 100%, 40% 100%, 45% 60%, 45% 40%);
}

The result looks like this:

Screenshot showing the result of applying the clip-path property to a shaped element to clip out excess parts of the image outside the defined shape.

The clip-path property is supported with prefixes at this time, so it will work in Chrome with the -webkit- prefix added to the clip-path property. You can check the live demo out here.

The clip-path property is an excellent companion to the shape properties, as it helps visualize the created shapes and clip out any parts of the element that are outside the defined shapes, so you will probably find yourself using it a lot in conjunction with the Shapes properties.

The polygon() function also takes in an optional fill keyword, which can be either nonzero or evenodd. This specifies how to treat areas inside the polygonal shape that may intersect itself. See the SVG fill-rule property for details.

Defining a shape using an image

To define a shape using an image, the image needs an alpha channel from which the browser can extract the shape.

A shape is defined by the pixels whose alpha value is greater than a certain threshold. That threshold defaults to a value of 0.0 (fully transparent), or you can set it explicitly using the shape-image-threshold property. So, any pixel that is not transparent will be used as part of the shape defined from the image.

A future level of CSS Shapes may define a switch to use the luminance data from an image instead of the alpha data. If this happens, shape-image-threshold will be extended to apply its threshold to either alpha or luminance, depending on the switch state.

We’re going to use the following image to define a shape on an element and have text wrap around it:

Using the shape-outside property with the url() value pointing to this image, we can flow the content around an element with this leaf shape.

.leaf-shaped-element {
	float: left;
	width: 400px;
	height: 400px;
	shape-outside: url(leaf.png);
	shape-margin: 15px;
	shape-image-threshold: 0.5;
	background: #009966 url(path/to/background-image.jpg);
	mask-image: url(leaf.png);
}

Of course, if you were to apply a background to the element, the background would need to be clipped outside the defined shape. The mask-image property (with appropriate prefixes) from the Masking Module can do that, since the clip-path property doesn’t take an alpha image as a value. The result looks like this:

Screenshot showing text wrapped around a shape extracted from an image with an alpha channel.

If you’re creating complex shapes, you might want to define a shape using an image. This allows you to use an alpha channel image in Photoshop instead of manually defining the points.

You will also want to use an image instead of a shape function when you have multiple float or exclusion areas inside an element—because you can’t, for now, declare multiple shapes on an element. But if the image contains multiple areas, the browser will extract these areas from the image and use them.

CSS Shapes in responsive design

Can CSS Shapes fit into our responsive workflows? The current specification for shape-outside has this covered, because it allows you to specify the dimensions of the element in either percentages or other length units, and the points defining the shape (parameters of the shape functions) can also be set in percentages. This means that an element with a shape-outside would be fully responsive, just like any other float with percentage dimensions.

shape-inside is not as responsive yet, but that’s because it has been pushed to Module Level 2. Many of its current limitations will be resolved in the next level.

Shape tools

Creating complex shapes using Shapes functions can be a daunting task, especially if you’re creating one with many points and coordinates using polygon(). Thankfully, Adobe’s web platform team has been working on interactive tooling that makes this much easier. Bear Travis has created a collection of Shape Tools that allow us to create polygonal shapes visually. The tool then generates the shape function for us. This is useful, but it can be limited if you want to create a shape based on a specific image, because there is no way for you to insert the image into the tool and then create the shape for it.

A more advanced and interactive Shapes tool has been developed by the Adobe Web Platform team. The tool has been recently released as an extension for Adobe’s free Brackets editor. It allows you to visualize and edit shapes directly in the browser, and has a live preview feature that updates the shape values in the stylesheet as you change them on the page. This gives you instant visual feedback for your changes, allowing you to see how your shapes interact with other elements on the page.

Screen capture showing how the new Shapes Editor can be used to edit Shapes right in the browser.
Editing a polygonal shape right in the browser using Brackets’ live preview mode. Screen capture courtesy of Razvan Caliman.

This tool will be indispensable because it facilitates creating, editing, and debugging our shapes. Razvan Caliman has written an article on the Brackets blog explaining how you can get the Shapes editor in Brackets and start using it today.

The future: CSS Exclusions

The CSS Shapes specification used to be the CSS Shapes and Exclusions spec, but the two specs were separated. While CSS Shapes defines the shape-inside and shape-outside properties, CSS Exclusions will define properties that allow us to wrap content around elements that are not floated, such as absolutely positioned elements. This will make it possible to make content wrap the entire shape from different sides, as shown in the following image.

Screenshot of a magazine layout with text wrapped around a pullquote.
In the future, CSS Exclusions will allow us to wrap text around a shape like a pullquote, as shown in this magazine layout. The pullquote can also be circular, and the text would wrap around it just as well. Image by Justin Thomas Kay.

Similar layouts with shaped elements positioned absolutely at the center of an article—with text flowing around the element on all sides—will also be possible.

Taking Shapes further

The current CSS Shapes specification is merely the first step. Soon, new options will give us more control over creating shapes and wrapping content in and around them, making it a lot easier for us to turn our mockups into live designs with just a few lines of code. Today, spec editors are focused on shipping shape-outside, and you’ll likely see CSS Shapes in more browsers by the end of 2014.

You can use Shapes today as part of a progressive enhancement workflow, knowing they have acceptable fallbacks in non-supporting browsers. I’ve recently started applying them to my own website, and the fallback is very “normal.” For more complex designs, you can use a script to check whether a browser supports Shapes, and provide any fallback you want if it doesn’t. You can also extend Modernizr’s tests with this script to test whether shape-outside is supported, or download a custom build including this test.

CSS Shapes allow us to bridge one more gap between print and web design. The examples in this article are simple, but should give you a foundation for creating designs as complex as any magazine or poster—without having to worry whether you’ll be able to recreate it on-screen. Whatever you explore—from non-rectangular layouts to animating Shapes—the time to experiment is now.

About the Author

51 Reader Comments

Load Comments