Angled Ribbon Folds With Masks and Gradients
In the first part of this series, we built responsive multi-line ribbons using a single element, repeating backgrounds, and clip-path. This time, we push the pattern further: the folds are no longer straight vertical strands but angled cuts, and the technique shifts from pure backgrounds to **CSS masks** for hiding and revealing the repeating shape.
Everything still lives in one element:
<h1>Your content here</h1>
The core repetition again comes from a repeating linear gradient. The key difference is coverage: we paint 80% of each line and leave the remaining 20% transparent, offsetting the background by .1lh so the visible color starts and ends cleanly within each line box. The value is hard-coded simply because it produced the most balanced look during testing.
h1 {
--c: #d81a14;
background: linear-gradient(var(--c) 80%, #0000 0) 0 .1lh / 100% 1lh;
}
Building the First Ribbon
The red ribbon in the demo is the more involved of the two. It breaks down into a base gradient layer, a darker folded overlay, and mask layers that carve the alternating triangular notches.
Base Color and Fold Depth
We stack two background gradients. The first is the repeating line color from the setup above. The second, placed behind it, simulates the darker fold: it uses the same color variable but mixes it with black via color-mix(). This gradient is shorter than the full element (calc(100% - .3lh)) so the darker fold never touches the top or bottom edges.
h1 {
--c: #d81a14;
padding-inline: .8lh;
background:
/* Gradient 1 */
linear-gradient(var(--c) 80%, #0000 0)
0 .1lh / 100% 1lh,
/* Gradient 2 */
linear-gradient(90deg, color-mix(in srgb, var(--c), #000 35%) 1.6lh, #0000 0)
-.8lh 50% / 100% calc(100% - .3lh) repeat-x;
}
Because the folds now angle inward on the sides, the text needs horizontal padding so it never runs into the cutout areas.
Masking the Zig-Zag Sides
The triangular shapes on the left and right repeat every two lines, not every line. We achieve this with two conic-gradient() masks, one for each side. Each gradient covers half the width (50%) and exactly two lines of height (2lh). Offsetting the left gradient by 1lh makes the two sides alternate as new lines are added or removed.
mask:
conic-gradient(from 225deg at .9lh, #0000 25%, #000 0)
0 1lh / 50% 2lh repeat-y,
conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0)
100% 0 / 50% 2lh repeat-y;
At this point, the mask hides the flat top and bottom cuts we eventually need to see. The top edge is filled by a third mask gradient — a simple linear gradient that restores the straight strip at the element’s start.
mask:
/* New gradient */
linear-gradient(45deg, #000 50%, #0000 0) 100% .1lh / .8lh .8lh no-repeat,
conic-gradient(from 225deg at .9lh, #0000 25%, #000 0)
0 1lh / 50% 2lh repeat-y,
conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0)
100% 0 / 50% 2lh repeat-y;
The bottom edge is trickier because the visible cut depends on whether the line count is odd or even:
We add two more mask gradients at the bottom. Their behavior is positional: on an even number of lines, the left mask covers the fold; on an odd number, the right one does. In the interactive demo below, the colored gradients show how the two sides trade places as the text wraps.
See the Pen [Illustrating the full mask configuration](https://codepen.io/t_afif/pen/RwvboJO) by Temani Afif.
Once all mask layers are colored identically to the background, the illusion is complete.
h1 {
--c: #d81a14;
padding-inline: 1lh;
mask:
linear-gradient(45deg, #0000 50%, #000 0)
0% calc(100% - .1lh) / .8lh .8lh no-repeat,
linear-gradient(-45deg, #0000 50%, #000 0)
100% calc(100% - .1lh) / .8lh .8lh no-repeat,
linear-gradient(45deg, #000 50%, #0000 0)
100% .1lh / .8lh .8lh no-repeat,
conic-gradient(from 225deg at .9lh, #0000 25%, #000 0)
0 1lh / 51% 2lh repeat-y,
conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0)
100% 0 / 51% 2lh repeat-y;
background:
linear-gradient(var(--c) 80%, #0000 0)
0 .1lh / 100% 1lh,
linear-gradient(90deg, color-mix(in srgb, var(--c),#000 35%) 1.6lh, #0000 0)
-.8lh 50% / 100% calc(100% - .3lh) repeat-x;
}
Variation: Transparent Borders and Edge Clips
The green ribbon is a close cousin of the first. It adds space above and below the pattern with **transparent borders** equal to .5lh, so the cutout sections have somewhere to live.
h1 {
--c: #d81a14;
border-block: .5lh solid #0000;
padding-inline: 1lh;
background: linear-gradient(var(--c) 80%, #0000 0) 0 .1lh / 100% 1lh padding-box;
}
Two additional gradients draw the vertical darker folds. They are set to cover the full area with border-box, each repeating every 2lh. The width is chosen to match the height of the horizontal line gradients, which is what keeps the folded edges visually connected.
The left and right edges again get the same alternating conic-gradient masks. To keep the top and bottom cutout strips visible under that mask, a third gradient in the mask fills those areas. Finally, a clip-path trims the two triangular ends at the bottom, guaranteeing the cutout is always exposed no matter the line count.
h1 {
--c: #d81a14;
padding-inline: 1lh;
border-block: .5lh solid #0000;
background:
linear-gradient(var(--c) 80%, #0000 0)
0 .1lh / 100% 1lh padding-box,
linear-gradient(#0000 50%, color-mix(in srgb,var(--c), #000 35%) 0)
0 0 / .8lh 2lh repeat-y border-box,
linear-gradient(color-mix(in srgb, var(--c), #000 35%) 50%, #0000 0)
100% 0 / .8lh 2lh repeat-y border-box;
mask:
linear-gradient(#000 1lh, #0000 0) 0 -.5lh,
conic-gradient(from 225deg at .9lh,#0000 25%,#000 0)
0 1lh/51% 2lh repeat-y padding-box,
conic-gradient(from 45deg at calc(100% - .9lh), #0000 25%, #000 0)
100% 0 / 51% 2lh repeat-y padding-box;
clip-path: polygon(0 0, calc(100% - .8lh) 0,
calc(100% - .4lh) .3lh,
100% 0, 100% 100%,
calc(100% - .4lh) calc(100% - .3lh),
calc(100% - .8lh) 100%, .8lh 100%, .4lh calc(100% - .3lh), 0 100%);
}
What Changed
These two patterns build directly on the previous article’s approach but introduce the missing pieces needed for more complex geometry. The most important additions:
- CSS masking —
maskwith repeatingconic-gradient()layers is what makes the alternating side notches possible without extra markup. - Multiple gradient roles — the same gradients that paint color also define which parts remain visible and which are clipped away.
- Even/odd line awareness — with gradients staggered by
1lh, the mask pattern automatically flips based on the number of lines the content occupies.
Both ribbons are responsive purely through CSS intrinsic sizing: change the element’s width or the text content, and the masks recompute the shape. The code is dense, but each gradient has a single job, and once you map which layer does what, variations like flipping the fold direction become an exercise in adjusting gradient positions rather than tearing up the structure.



