Ribbon Shapes That Grow With Their Text
Ribbon motifs have been a staple of web design since the early 2010s, when Chris Coyier shared a CSS snippet that quickly became ubiquitous. They add visual interest, whether as decorative headings, sale badges on product cards, or bookmark icons. But traditional implementations often fail when text wraps to multiple lines. The shape breaks because it is sized with fixed dimensions, not the content itself.
Modern CSS has changed that. With calc(), color-mix(), and trigonometric functions, we can now build ribbon shapes that adapt to any amount of text without JavaScript or magic numbers. A collection of over 100 such ribbon shapes demonstrates the approach. The core challenge is to use a single element that renders a proper ribbon whether it contains one line or ten.
The two ribbon variations we’ll build share nearly identical CSS — color and minor decorative details are what differentiate them. Both are constructed with layered background gradients and exactly one HTML element, such as an <h1>.
Gradient Construction: Line by Line
Gradients are the obvious tool, since they can paint the same pattern on every line of text. But first, we need a length unit that matches a line of text precisely. The new lh unit does exactly that, as 1lh equals the computed line-height. This allows a background gradient to sit behind every line with no gaps and no overlap.
Note: Safari computes lh based on the parent element’s line-height rather than the element itself. Explicitly setting a line-height on the enclosing parent, like the body, works around this until the behavior is corrected.
The first gradient is a simple rectangle. Set the color fill to 70% of the line height and leave 30% transparent to create breathing space between lines. Positioning the gradient at a negative offset equal to half that space keeps the color perfectly centered.
A second gradient handles the slanted ends where the color seems to peel back on itself. This layer sits behind the first gradient. Instead of relying on degrees, the direction uses keywords like to bottom right, with a color stop that begins at the diagonal point, or 50%.
Choosing where the hard color stop lands on that diagonal requires some geometry. If the gradient runs at 45 degrees for one full line height, placing a second value at 85% yields the precise angle needed for the fold. The exact stop point comes from the ratio of vertical alignment to horizontal length, then converts to a percentage for the CSS function.
Outputting a darker or lighter shade for the sloping edge requires no extra color variables when using color-mix(). Mixing the main color with black via this function produces a darker shade within the gradient definition itself, keeping the markup clean and reducing variable overhead.
The initial result looks unfinished — background repetitions extend beyond the text at the top and bottom — but clipping will solve that next.
The First Ribbon: Simple Triangle Cutout
Applying a single clip-path cuts triangular notches from the ends and simultaneously removes unwanted gradient repetition at the top and bottom.
The polygon() function takes coordinates mapped to the element’s bounding box. Whether that bounding box is full width or content width depends on how the CSS variable controlling width is applied, but in general, 0 and 100% represent the left and right edges, or the top and bottom. Line height and half-line-height offsets express vertical positions relative to text lines.
Six to eight pairs of coordinates in sequence complete the path. Some points start inside the rectangle for the notch, then extend to the full width only after cutting around a corner. This shape produces the finished red ribbon design immediately.
The Second Ribbon: Folded Ends Back
While the first ribbon relies solely on clips, the second requires both pseudo-elements to create folds at the start and end that rotate in and out of view.
Three steps are needed:
- Position two rectangular folds at each edge.
- Rotate them by a variable-defined angle,
--a. - Apply clips to pseudo-elements to shape a triangular cutout and remove overflow from the gradient layer.
Common properties, positional columns, and clipping responsibilities are distributed across the shared rule and separate rules for :before and :after.
The fold rectangles extend half a line height vertically — written with calc() as half of 1lh — with their width derived from rotating half the horizontal thickness. Positioning them zero from an edge handles left versus right variations. Extensions of depth come from half of a fixed size minus the diagonal taper, combined with the triangle bisector angle from two right triangles.
Trigonometric functions now have CSS-level grammar, so expressions like sine, cosine, and tangent appear commonly in the snippet. Their inputs use the previously declared --a and halved totals. Math signs in front invert the height and rotation orientation for opposite sides.
A final clip-path on the parent element removes those leftover strips of repeated gradient that project outward from the shape, as well as small slivers where the folded pieces intersect the line block. With skewed clip coordinates defined by all these calculations, both top and bottom are cleaned simultaneously.
A Reproducible Template
These two designs are effectively the same frame with different colors, rotation angles, cut depths, and decoration. A user adjusts a handful of CSS custom properties — the main color, ribbon thickness, notch depth, and angle — to load different visual variations without touching underlying markup or font sizing rules.
From a beginner to a reference manual, both examples illustrate how overlapping gradient layers, pseudo-elements, recent unit types, and mathematical functions replace brittle, fixed-size hacks. Every calculated percent updates itself when paragraph lengths shift. The same structure recurs across the related source code collections.
The next part of this series extends the same principles into yet more complex folds and floating shapes, encouraged by the flexibility available since these features reached consistent browser support.



