Layouts in CSS: Outside-the-box Layouts with CSS Shapes

Everything is a box in CSS, but with CSS Shapes we can sometimes have circles and triangles. The CSS Shapes specification makes it possible to flow content around non-rectangular shapes.

Shaped elements must be floated. Remember: when you float an element, content will flow along its right or left edge. When an element is shaped, content instead flows along the edges of the shape. Shaped elements must also have a width and height. Width and height can either be explicitly set, derived from the element’s padding value, or derived from the size of its contents.

Let’s look at a simple example of flowing text around a circle. Here’s our (abbreviated) HTML:

<p>

Etiam pharetra nibh tempus, viverra sem vel, eleifend eros. Integer semper lorem odio, ac feugiat mi tincidunt et. Donec luctus ante sit amet elementum facilisis. Suspendisse potenti. In varius eros at mollis eleifend….

</p>

<p>

Phasellus blandit elit vitae euismod volutpat. Maecenas venenatis sed leo euismod aliquam.

</p>

We’ll pair it with the following CSS:

p:first-of-type::before {

content: ‘ ‘;

float: left;

width: 40rem;

height: 40rem;

shape-outside: circle( 50% );

}

In this example, our rule set floats the element and explicitly sets its width and height. We’ve defined our actual shape using the shape-outside property. shape-outside defines the type of shape, along with its size and position. The above CSS results in the layout shown below.

Using shape-outside generates a reference box. This reference box functions as a sort of coordinate system in which the shape is drawn. Even though content flows around the shape, the reference box still exists. You can see it by adding a background color to p:first-of- type::before .

Shapes can be defined in one of two ways:

  • using the eLLipse() , circLe() , inset() , path() , or poLygon() basic shape functions
  • using an image with an alpha channel—that is, an image with full or partial transparency, including gradients

Let’s look at shape functions next.

1. Using Shape Functions

Shapes are a type of CSS value, like length, angle, or color units. Here, we’re going to discuss them in the context of shape-outside , but they also apply to the clip-path and offset- path properties. Some of the examples in this section will use cLip-path() to illustrate the contours of the shape.

This section uses value definition syntax to explain the arguments for each of these functions. I’ve lifted each function definition directly from the CSS Shapes specification. Value definition syntax is a bit awkward to read, but it’s far clearer than saying “it accepts from one to six arguments, the first four of which use the same order and shorthand as margin , and the last two of which are really one argument that sets a border radius.” That is, by the way, an explanation of the inset() function.

Value definition syntax is easy to read once you get the hang of it. If you’re familiar with regular expressions, some of it will be familiar.

The image above illustrates a value definition statement using a generic example. Mozilla Developer Network has a far more thorough explanation if you want more details.

ellipse() and circle()

The eLLipse() function creates an ellipse or oval around two focal points within the reference box. Its arguments use the following pattern:

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

Both sets of arguments for eLLipse() are optional, and the center point of the reference box (50%, 50%) is the default value for both. Yes, shape-outside: eLLipse() is perfectly valid. It creates a circle.

The first set of arguments indicates the x-radius and y-radius positions around which the ellipse will be drawn. If you include one, you must include the other. Here’s an example:

.ellipse {

float: left;

width: 40rem;

height: 40rem;

shape-outside: ellipse( 20% 40% );

/* Added to illustrate the ellipse and how text flows around it */ background-color: #666;

clip-path: ellipse( 20% 40% );

}

The second argument must begin with the at keyword, and it indicates the position of the center point for the ellipse. Let’s set a position for our ellipse:

.ellipse {

float: left;

width: 40rem;

height: 40rem;

shape-outside: ellipse( 20% 40% at 0% 50% );

/* Added to illustrate the ellipse and how text flows around it */

background-color: #666;

clip-path: ellipse( 20% 40% at 0% 50% );

}

Adding a position value to the ellipse function shifts where the center point of the ellipse sits within the shape’s reference box. Now the center point for our ellipse sits completely to the left of the reference box, and halfway down.

Position values can be lengths or percentages. Or you can use position keywords like Left, bottom , right , top ,and center .

The circLe() function is a simpler version of eLLipse() . Rather than requiring an x-radius and y-radius, circLe() instead accepts a single shape radius argument. Using circLe(30%) is the same as eLLipse(30% 30%) .

inset()

We can create rectangular shapes with the inset() function. I suspect you’re wondering why we need a rectangular shape when floats are already rectangular. Well, floats don’t let you create neat inset effects, nor do they cause text to flow around a rectangle’s rounded corners. With inset() we can do both. Let’s update our example from earlier in the chapter. We’ll change our shape from circLe() to inset() :

p:first-of-type::before {

content: ‘ ‘;

float: left;

width: 40rem;

height: 40rem;

shape-outside: inset( 10% 20% 20% 0 round 8rem );

background: hsl( 271, 76%, 83% );

}

Using inset() with a border radius causes text to flow around the borders’ curves, as illustrated below.

Arguments for inset() use the following syntax:

inset( <length-percentage>{1,4} [ round <‘border-radius’> ]? )

The inset() function requires at least one length or percentage value, but accepts as many as four, representing the top, right, bottom, and left offsets from the reference box. These arguments mirror the syntax of the margin shorthand property. Using two values, for example, sets the top/bottom and right/left offsets. Using three values sets the top/bottom and right offsets.

The inset() function also accepts an optional border-radius argument, indicated by the round keyword, and followed by a valid border radius value.

polygon()

We can use the poLygon() function for more complex shapes. A polygon is a closed shape made from straight lines—which includes triangles, rhomboids, stars, and octagons. The poLygon() function takes arguments in the following form:

polygon( <‘fill-rule’>? , [<length-percentage> <length-percentage>]# )

Its fiLL-ruLe argument is optional. It should be nonzero or evenodd , but defaults to nonzero if omitted. The second argument should be a series of length or percentage values that describe the shape. Let’s create a triangle, this time floated to the right:

p:first-of-type::before {

content: ‘ ‘;

float: right;

width: 60rem;

height: 60rem;

shape-outside: polygon( 100% 0, 0% 50%, 100% 100% );

/* Helps visualize the shape */

clip-path: polygon( 100% 0, 0% 50%, 100% 100% );

background-color: hsl( 182, 25%, 50%, .25 );

}

The image below shows the result—a triangle-shaped float created using the poLygon() function, and floated to the right.

Notice here that some languages and writing directions may cause text to flow less tightly around a floated shape. The language in this example (Latin) uses a left-to-right language direction, and spaces to mark the beginning and ending of words. As a result, our lines differ in length, and our shape is less clearly articulated. Use word-break: break-all or text-align: justify to mitigate this.

If you want to use complex, multi-sided polygons, or shapes that include curves and arcs, these basic shapes can be quite limiting. Luckily for us, we aren’t limited to using shapes with shape-outside .

2. Using Images

We can also use images to define shapes for use with shape-outside . You can use any image with an alpha channel. This includes PNG images, SVG images with HSLA or RGBA fill values, and CSS gradients. It doesn’t, however, include image formats such as GIF, which only support binary transparency. Such formats are incompatible with shape-outside .

The image below illustrates the use of shape-outside with a PNG image that contains an alpha channel.

Mug of coffee photo by Foodie Factor from Pexels.

Let’s look at the CSS used to create the layout for A Good Cuppa Joe (pictured above). In this example, we’ve added the shape-margin property, which adds space around the shape. Unlike margin , shape-margin accepts a single length or percentage value that’s applied around the contours of the shape:

[src=’coffee-cup.png’] {

float: right;

width: 615px;

height: 569px;

shape-outside: url( ‘coffee-cup.png’ );

shape-margin: 2rem;

}

The CSS above is paired with the markup shown below. It’s been abbreviated for the sake of space:

<p>

<img src=”coffee-cup.png” alt=”a cup of coffee on a saucer with a spoon”>

Etiam pharetra nibh tempus, viverra sem vel, eleifend eros. Integer semper lorem odio, ac feugiat mi tincidunt et. Donec luctus ante sit amet elementum facilisis. Suspendisse potenti.

In varius eros at mollis eleifend. …

</p>

<p>

Phasellus blandit elit vitae euismod volutpat….

</p>

The trick to getting text to flow along the curved edge of coffee-cup.png is to apply the shape-outside property to the image element, and set the image’s URL as its value. But it’s not the image itself that causes text to flow along the image curve. It’s the combination of float and shape-outside . Remove the <img> tag and change the selector to p:first-of- type::before , and there will be an empty, rounded space where the cup used to be. The image below shows how the combination of float and shape-outside causes text to flow around the curve of the image, whether the image is included in the markup or not.

When using external images with shape-outside , the browser makes a potentially CORS- enabled fetch for the resource. The browser must be able to resolve the document’s path in such a way that it can generate an origin. In practical terms, this means two things:

  • Linked and external images need to share the same origin as your document, or be served with the correct Access-Control-* response headers.
  • You’ll need to use a server or content delivery network when developing or viewing layouts that use shape-outside so that the browser can create an origin.

You’ll need to use a web server even during local development. Loading files directly from your computer’s file system won’t work.

3. CSS Gradients Are Images Too!

Image files and data URIs are not the only images we can use with shape-outside . CSS gradients also work. The image below shows an example of shape-outside combined with background-image with a CSS gradient to create a beach-like page design.

To create it, we’ve used the following CSS:

body {

background-color: hsl( 183, 80%, 80% );

}

body::before {

content: ‘ ‘;

display: block;

float: right;

height: 100vh;

width: 50%;

background-size: cover;

background-image:     linear-gradient( -45deg, hsl( 240, 86%, 25% ) 0%, hsla( 213, 100%, 50%,0.8 ) 22%, hsla( 183, 100%, 50%, 0 ) 53% );

shape-outside:        linear-gradient( -45deg, hsl( 240, 86%, 25% ) 0%, hsla( 213, 100%, 50%,0.8 ) 22%, hsla( 183, 100%, 50%, 0 ) 53% );

}

In this case, the text flows along the edge of the gradient at the point where it becomes fully transparent ( hsLa( 183, 100%, 50%, 0 ) ).

We can adjust the point at which the text flows by adjusting the value of the shape-image-threshold property. shape-image-threshold represents an alpha value between 0 and 1, inclusive. Portions of the image with an alpha level that exceeds this threshold will be included in the shape. Adding shape-image-threshold: 0.4 to the preceding CSS changes the size of the triangle created by the linear gradient.

Changing the alpha threshold means that text now flows around areas of the gradient that have a transparency value of 0.4 or higher. These are interpolated values between hsla(213, 100%, 50%, 0.8) (alpha = 0.8) and hsla(183, 100%, 50%, 0) (alpha = 0).

4. The Shape of the Future (or the Future of Shapes)

As you may have figured out from the shape-outside property name, CSS Shapes is concerned with flowing text around floated items. Content inside the shaped element does not, in fact, follow the contours of the inside of the shape. Instead, that content is contained by the dimensions of the reference box.

Level 2 of the CSS Shapes Module specification defines shape-inside and shape-padding properties that do affect the shape of content inside the box. The image below shows what a layout that uses shape-inside might look like.

Some day, we may be able to create layouts like the one pictured above, in which the text inside the shape follows its contours. It’s not clear how soon these features will be implemented, however. That specification is still in its early stages.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *