CSS Background Pattern Generator

Six repeating patterns built from gradients rather than images, previewed with real text on them, since a background is judged by what sits on it.

Enable JavaScript to change the pattern; the CSS below is for the defaults.

The repeat distance. Everything else is a share of this, so one slider changes the scale of the whole pattern.

How much of each tile the ink covers: stripe width, line weight or dot diameter, depending on the pattern.

Used by stripes and the diagonal, which is the same gradient rotated 45 degrees rather than a rotated element.

A pattern behind text usually wants to be much fainter than it looks in isolation.

Text on the pattern

A pattern is a background, so the contrast that matters is between this text and the darkest part of the pattern rather than between the two pattern colours.

The small square is one tile at the same size, which is where a seam shows if there is one.

pattern.css
.patterned {
	background-color: rgb(255 255 255);
	background-image: repeating-linear-gradient(0deg, rgb(30 86 236 / 0.14) 0 6px, transparent 6px 24px);
}

/* The pattern sits behind the text, so the contrast that matters is
   between the text and the darkest part of the pattern, not between the
   two pattern colours. Check it at the ink colour rather than the paper. */

Every pattern here is made of gradients and a background-size. No request, no image to optimise, no second file to keep in sync with the design, and the colour can come from a custom property, which a tiling PNG cannot do.

The preview puts real text on the pattern, because that is the only question worth asking about a background. A pattern that looks confident on its own is often unreadable behind a paragraph, and the contrast that matters is between the text and the darkest part of the pattern rather than between the two pattern colours. That is why the ink opacity defaults to 14 percent: much fainter than it looks in isolation is usually correct.

How to use

  1. Pick a pattern and set the tile size. Everything else is a share of that, so one slider changes the scale of the whole thing.
  2. Set the ink share, which is stripe width, line weight or dot diameter depending on the pattern.
  3. Drop the ink opacity until the text on the swatch is comfortable, then check the small tile for a seam.

Example

Faint blue stripes on white:

.patterned {
	background-color: rgb(255 255 255);
	background-image: repeating-linear-gradient(0deg, rgb(30 86 236 / 0.14) 0 6px, transparent 6px 24px);
}

/* The pattern sits behind the text, so the contrast that matters is
   between the text and the darkest part of the pattern, not between the
   two pattern colours. Check it at the ink colour rather than the paper. */

And the dot grid, which is two declarations doing what a repeating image used to:

.patterned {
	background-color: rgb(255 255 255);
	background-image: radial-gradient(circle at center, rgb(11 16 32 / 0.2) 0 3px, transparent 3px);
	background-size: 18px 18px;
}

Pitfalls

Read the text, not the pattern. A background is judged by what sits on it. If the paragraph is harder to read, the pattern is too strong however good the swatch looks.

Fainter than you think. Behind body text, ink opacity in the 5 to 15 percent range is usually the whole answer. Above about 25 percent it competes with the content.

Check the tile, not the swatch. A seam shows at the tile boundary. The small square in the preview is one tile at the same size, which is where a mismatch becomes visible.

Diagonals alias. A hard colour stop on a diagonal produces a stepped edge, particularly at small sizes. A slightly larger tile or a lower contrast hides it; so does a one-pixel soft stop if you hand-edit.

background-attachment: fixed is expensive. It looks good and it forces a repaint of the background as the page scrolls. On a phone that is where a smooth page stops being smooth.

Patterns and dark mode need separate values. The same ink at the same opacity over a dark surface reads completely differently. Use a custom property for the ink and set it per theme.

A pattern is decoration. Mark the element aria-hidden if it exists only to be looked at, and never put information in it.

Compatibility

repeating-linear-gradient, radial-gradient, background-size and multiple background images work in every browser in use, including old Safari and Edge.

The multi-stop shorthand used here, #000 0 6px, is the double-position syntax supported in Chrome, Firefox and Safari since 2017 or so. If you need to support something older, expand each stop into two.

The declarations are built by one function, mirrored in PHP for the first paint, with a case file comparing the two on every build across all six patterns, a fixed attachment, a fractional ink share and an unknown pattern name.

The tile size is floored at one pixel, so a zero cannot produce a division by nothing, and the ink opacity runs through the same colour helper as the shadow tools, which is why a solid colour prints as rgb() with no alpha rather than as / 1.

Frequently asked questions

Why gradients instead of an SVG?
Either works. Gradients need no file, no encoding and no base64 string in your stylesheet, and they take their colours from custom properties. An SVG is better for anything genuinely illustrative.
How do I change the colour per theme?
Put the ink in a custom property and set it in your dark mode block. The gradient stays the same and the colour follows the theme.
Why does my pattern look blurry?
Usually a fractional tile size on a non-integer device pixel ratio. Whole pixel sizes help. So does avoiding a transform: scale() on an ancestor.
Can I animate a pattern?
Animate background-position and the pattern appears to drift. It is cheap enough for a hero and it belongs inside a prefers-reduced-motion check.
Which pattern is safest behind text?
A faint dot grid or fine stripes. Checks and zigzags have large areas of ink and a strong direction, which compete with lines of text far more than a small repeated mark does.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.