How to Create CSS Ribbon Shapes with a Single Element — SitePoint

By admin
In this article, I’ll show you how to use modern CSS tricks to create fancy CSS ribbon shapes with minimal code. As an extra bonus, our ribbons will have hover animations!

CSS ribbons are everywhere, and you can find a ton of articles about them, but the ones we’ll create here are a bit special. We’re going to rely on a single element to create each of the shapes, and CSS variables to easily control them. We aren’t going to rely on fixed dimensions or magic numbers. The shapes will fit their content so you don’t have to worry about the text inside.

I’ve made a collection of

CSS
ORG

ribbon shapes with a lot of cool examples, and in this article, we’re going to study

two
CARDINAL

types of them, pictured below.

I’ll be calling the left one the “folded ribbon” and the right one the “rotated ribbon”.

Creating a CSS Folded Ribbon Shape

The

first
ORDINAL

step in creating our folded CSS ribbon is to define the variables of our shape.

.ribbon { –r :

20 px
QUANTITY

; –s :

20 px
QUANTITY

; –c : #d81a14 ; }


Two
CARDINAL

variables will control the shape, and

one
CARDINAL

variable will control the color.

Now let’s move to the code. We’re mainly going to rely on clip-path . The image below illustrates the polygon shape we’re going to use.

We add some padding to avoid cutting the text, then we apply the clip-path :

.ribbon { –r :

20 px
QUANTITY

; –s :

20 px
QUANTITY

; –c : #d81a14 ; line-height :

1.6
CARDINAL

; padding-inline :

1.2
CARDINAL

lh calc ( var ( –r ) + .2 lh ) ; background : var ( –c ) ; clip-path : polygon (

1
CARDINAL

lh 0 ,

100 %
PERCENT

0 , calc (

100 %
PERCENT

– var ( –r ) )

50 %
PERCENT

,

100 %
PERCENT


100 %
PERCENT

,

100 %
PERCENT


100 %
PERCENT

,

0 100 %
PERCENT

,

0 100 %
PERCENT

) ; }

Using the CSS lh unit

You may be wondering what’s going on with the lh unit. It’s a new unit that corresponds to the line-height value. Since we’re using

one
CARDINAL

line of text, the line-height setting is what controls the height, so

1lh
CARDINAL

is equivalent to the height of the element, which is super useful. (You can read more about the lh unit in An Overview of CSS Sizing Units.)

In clip-path , I need to cut the shape of an isosceles triangle, and to do this I need to know the height of the element.

1lh
CARDINAL

is equal to that height.

Now, to create the folded part, we’re still going to use clip-path and update the previous polygon. The cool thing about clip-path is that it can cut “outside” the boundaries of the element. It may sound surprising or maybe useless, given that we have nothing outside, but it means we can include things like box-shadow, outline, pseudo-elements, and so on.

In our case, we’ll rely on box-shadow . The image below illustrates the trick.

Note how I’m updating the clip-path to include

four
CARDINAL

new points,

three
CARDINAL

of which are outside the element. Since the part we’re cutting is outside, it’s not visible, but if we add a big box-shadow we make if visible. I’ve used a blue color to illustrate the idea above, but in the code we’ll use the same color as the background:

.ribbon { –r :

20 px
QUANTITY

; –s :

20 px
QUANTITY

; –c : #d81a14 ; line-height :

1.6
CARDINAL

; padding-inline :

1.2
CARDINAL

lh calc ( var ( –r ) + .2 lh ) ; background : var ( –c ) ; clip-path : polygon (

1
CARDINAL

lh 0 ,

100 %
PERCENT

0 , calc (

100 %
PERCENT

– var ( –r ) )

50 %
PERCENT

,

100 %
PERCENT


100 %
PERCENT

, 1 lh

100 %
PERCENT

,

1
CARDINAL

lh calc (

100 %
PERCENT

+ var ( –s ) ) ,

.5
CARDINAL

lh calc (

100 %
PERCENT

+ var ( –s ) + var ( –r ) ) ,

0
CARDINAL

calc (

100 %
PERCENT

+ var ( –s ) ) ,

0 100 %
PERCENT

) ; box-shadow :

0 0 0 999
CARDINAL

px var ( –c ) ; }

Finally, we add a touch of shadow effect by introducing a gradient and another box-shadow and we’re done. Our

CSS
ORG

ribbon shape is perfect!

You’re probably wondering how to create the

second
ORDINAL

ribbon (the green one). We do the same thing but with a different polygon. We take the

first
ORDINAL

polygon and we invert it.

A polygon can be written like so:

clip-path : polygon (

X1 Y1
ORG

,

X2 Y2
ORG

, … ,

Xn Yn
PERSON

)

To get the opposite shape, you change all

Xi
PERSON

by

100%
PERCENT

– Xi . As simple as that! Before checking my code, try to do it alone using the polygon of the

first
ORDINAL

ribbon.

In the demo above, hover the shapes to notice a nice animation. To achieve it, we need to update the polygon on hover by offsetting some points. I won’t re-write the whole polygon on hover, but I will define a CSS variable that will control the offset.

If you focus on the animation, you’ll notice that we have

three
CARDINAL

points moving to the left and

three
CARDINAL

points moving down and to the left as well.

We update the

Xi
PERSON

of the points moving to left with

Xi
PERSON

+ d and we update the Yi of the points moving doing with

Yi +
PERSON

d . Then we simply update the variable d to control the movement:

.ribbon { –d :

0 px
CARDINAL

; clip-path : polygon ( calc (

1
CARDINAL

lh + var ( –d ) )

0
CARDINAL

,

100 %
PERCENT

0 , calc (

100 %
PERCENT

– var ( –r ) )

50 %
PERCENT

,

100 %
PERCENT


100 %
PERCENT

, calc (

1
CARDINAL

lh + var ( –d ) )

100 %
PERCENT

, calc (

1
CARDINAL

lh + var ( –d ) ) calc (

100 %
PERCENT

+ var ( –s ) + var ( –d ) ) , calc (

.5
CARDINAL

lh + var ( –d ) ) calc (

100 %
PERCENT

+ var ( –s ) + var ( –r ) + var ( –d ) ) , var ( –d ) calc (

100 %
PERCENT

+ var ( –s ) + var ( –d ) ) , var ( –d )

100 %
PERCENT

) ; } .ribbon :hover { –d : .2 lh ; }

If you see such a polygon for the

first
ORDINAL

time, you may get confused, as it looks a bit scary. But in reality, it’s not that complex. We started with a simple polygon and we slowly added more points and more calculations until we reached this complex one.

Creating a Rotated CSS Ribbon Shape

Let’s tackle the

second
ORDINAL

shape. For this one, we’ll use the new trigonometric functions along with CSS variables and calc() like the previous one. To understand the logic behind this shape, let’s rotate it and keep the text in a straight line.

I’m adding a bit of transparency to see the parts behind the main element. I’ll be using pseudo-elements to create them. I’ve also added the blue outline to illustrate the area of the element. This shape will be controlled with

two
CARDINAL

variables:

.ribbon { –r :

30 px
QUANTITY

; –a :

15 deg
QUANTITY

; }

The r is doing the same job as with the previous shape. The a will control the rotation of the main element and a lot of other things.

Let’s start with the main element. We can see from the figure that we need to cut it from each side, so you may logically think about using clip-path , but not this time. We’ll rely on a gradient coloration, where the part we need to cut will have a transparent color:

.ribbon { –r :

30 px
QUANTITY

; –a :

15 deg
QUANTITY

; background : linear-gradient ( calc (

90 deg
QUANTITY

+ var ( –a ) ) , #

0000
MONEY

calc (

1
CARDINAL

lh * sin ( var ( –a ) ) ) , var ( –c )

0
CARDINAL

calc (

100 %
PERCENT

– 1 lh * sin ( var ( –a ) ) ) ,

#0000 0
MONEY

) ; }

Here comes the geometry.

The a is the angle variable we defined. Considering this, the gradient needs to have an angle equal to

90deg +
CARDINAL

a , and the transparent color should start at

0
CARDINAL

and stop at d . Doing some math, d is equal to 1lh*sin(a) . If we apply the same logic on the other side, we get the following code:

background : linear-gradient ( calc (

90 deg
QUANTITY

+ var ( –a ) ) ,

#0000 0 %
MONEY

calc (

1
CARDINAL

lh * sin ( var ( –a ) ) ) , var ( –c ) calc (

1
CARDINAL

lh * sin ( var ( –a ) ) ) calc (

100 %
PERCENT

– 1 lh * sin ( var ( –a ) ) ) , #

0000
MONEY

calc (

100 %
PERCENT

– 1 lh * sin ( var ( –a ) ) )

100 %
PERCENT

) ;

We do some optimization by removing the

0% and 100%
PERCENT

(they’re implicit), and when we have

two
CARDINAL

consecutive color stops that are equal, we can replace the

second
ORDINAL


one
CARDINAL

with

0
CARDINAL

:

background : linear-gradient ( calc (

90 deg
QUANTITY

+ var ( –a ) ) , #

0000
MONEY

calc (

1
CARDINAL

lh * sin ( var ( –a ) ) ) , var ( –c )

0
CARDINAL

calc (

100 %
PERCENT

– 1 lh * sin ( var ( –a ) ) ) ,

#0000 0
MONEY

) ;

We’re done with the main element, so let’s move to the pseudo-elements. Here as well, we need some geometry tricks to identify the size.

We can easily find the height H from the previous figure we used to identify the gradient configuration. It’s equal to

1lh
CARDINAL

/

cos(a
PERSON

) . For the width W, it’s equal to (

100%
PERCENT

– x)*cos(a) , where

100%
PERCENT

is the width of the main element and x is that small part where we have the transparency. It’s equal to

1lh*tan(a
CARDINAL

) .

Both pseudo-elements have the same size, so our code is as follows:

.ribbon :before , .ribbon :after { content : "" ; position : absolute ; height : calc (

1
CARDINAL

lh / cos ( var ( –a ) ) ) ; width : calc (

100 %
PERCENT

* cos ( var ( –a ) ) – 1 lh * sin ( var ( –a ) ) ) ; }

If you’re not comfortable with the math and you’re a bit lost with the formula, it’s fine. You don’t need to accurately understand them. The goal is to be able to adjust the shape using CSS variables. The formulas are here to make things easier and avoid us dealing with hard-coded values and magic numbers.

After the dimension, we should correctly place each pseudo-element, rotate it, and use clip-path for the cutout part:

.ribbon :before , .ribbon :after { content : "" ; position : absolute ; transform : translate3d (

0
CARDINAL

,

0
CARDINAL

, -1 px ) ; rotate : var ( –a ) ; height : calc (

1
CARDINAL

lh / cos ( var ( –a ) ) ) ; width : calc (

100 %
PERCENT

* cos ( var ( –a ) ) – 1 lh * sin ( var ( –a ) ) ) ; background : color-mix ( in srgb , var ( –c ) ,

#000
MONEY


40 %
PERCENT

) ; } h1 :before { right :

0
CARDINAL

; top :

0
CARDINAL

; transform-origin : top right ; clip-path : polygon ( 0 0 ,

100 %
PERCENT

0 ,

100 %
PERCENT


100 %
PERCENT

,

0 100 %
PERCENT

, var ( –r )

50 %
PERCENT

) ; } h1 :after { left :

0
CARDINAL

; bottom :

0
CARDINAL

; transform-origin : bottom left ; clip-path : polygon ( 0 0 ,

100 %
PERCENT

0 , calc (

100 %
PERCENT

– var ( –r ) )

50 %
PERCENT

,

100 %
PERCENT


100 %
PERCENT

,

0 100 %
PERCENT

) ; }

The code should be self-explanatory and the clip-path value should be easy to understand. We used more complex polygons with the

first
ORDINAL

shape.

Note the use of color-mix() , which allows me to create a dark version of the main color. I’m also using a 3D translate with a negative value on the z-axis to bring the pseudo-elements behind the main element. You might think that this is the job of z-index , but it won’t work due to some stacking context issues that I’ve detailed in this Stack Overflow thread.

Now, if we rotate the element in the opposite direction, we get our

CSS
ORG

ribbon shape.

As with the previous example, I’ll let you dissect the code of the green ribbon and see what changes I made to get the opposite shape.

Conclusion

It was a fun exercise, don’t you think? We explored some modern CSS features like CSS variables, calc() , and trigonometric functions, and we combined them to create fancy ribbon shapes.

If you want more, go check out my full collection of ribbon shapes. Try to build some of them alone before checking the code. It will be a good exercise to practice what you’ve learned here.