Skip to main content

Pure-CSS Background Patterns: Build a CSS Pattern Without a Single Image

How to make dots, stripes, grids and checkerboards as pure-CSS background patterns with repeating-linear-gradient — resolution-free, tiny, and zero image files.

Published By Li Lei
#css #frontend #design #background-pattern

Pure-CSS Background Patterns: Build a CSS Pattern Without a Single Image

For years the reflex for a textured background was to open an image editor, draw a tiny tile, export a PNG, and drop it into background-image: url(...). It works, but it ships a binary file, blurs on high-DPI screens, and forces a new export every time a designer wants a different colour. There is a better way, and it has been shipping in browsers since around 2017: build the pattern out of CSS gradients. No raster file, no extra HTTP request, and it recolours by editing two hex values.

This post walks through how a CSS background pattern is actually constructed, why gradients beat image tiles for most cases, and a copy-paste example you can drop into a stylesheet today.

How gradients become a repeating pattern

The trick is repeating-linear-gradient. A normal linear gradient blends one colour into another across the whole box. The repeating variant does the same thing but loops the colour stops over a fixed distance, tiling the band across the element forever. The second insight is the hard colour stop: when two stops sit at the same position with no blend between them, you get a crisp edge instead of a fade. Two colours plus two coincident stops gives you a clean stripe with no anti-aliased mush.

Once you have stripes, everything else is composition. Angle the stripe band and you get diagonals. Layer two stripe stacks at 90 degrees to each other and you get a grid or a checkerboard. Swap the linear gradient for radial-gradient tiled with background-size, and the same idea makes a field of dots. The whole vocabulary of geometric backgrounds — dots, grid, stripes, checkerboard, diagonal, cross — comes out of stacking and angling a couple of gradient functions.

That is the load-bearing idea worth repeating: a hard-color-stop repeating gradient makes stripes, and layering and angling those stripes builds checks and grids. No image file is ever involved.

Why CSS patterns beat image tiles

Three concrete wins push me toward gradients on almost every project.

Resolution-free. A gradient is math, not pixels. It renders at the device's native resolution every time, so it stays razor-sharp on a 4K monitor or a Retina phone. A 16×16 PNG tile, by contrast, either softens under upscaling or you ship a 2x and 3x version and juggle them with media queries.

Tiny. A typical pattern is roughly 60 to 120 bytes of CSS. A comparable PNG tile is a kilobyte or more and a separate network round trip that can block paint. When the pattern lives in your stylesheet, there is nothing extra to fetch and nothing to wait on.

Instant recolouring and theming. Want a dark-mode variant? Swap the two hex values. There is no asset pipeline, no re-export, no public/ folder churn. One spacing value controls the whole density, so when a designer asks for a tighter grid you change a number instead of redrawing a tile.

The honest trade-off: organic textures — paper grain, hand-drawn noise, photographic fuzz — are genuinely hard to fake with gradients. For those, an SVG or an image is still the right tool. But for clean geometry, gradients win on every axis that matters.

A worked example: a striped section background

Here is a diagonal caution-tape stripe you can paste onto any element. It is one declaration:

.warning-banner {
  background-image: repeating-linear-gradient(
    45deg,
    #1c1c1c 0,
    #1c1c1c 16px,
    #ffe27a 16px,
    #ffe27a 36px
  );
}

Read it left to right. 45deg angles the stripes. The first two stops paint dark grey (#1c1c1c) from 0 to 16px — that is the stripe width. The next two stops paint yellow (#ffe27a) from 16px to 36px. Because the gradient repeats, that 36px band tiles across the whole element forever. Notice the coincident stops at 16px: dark ends and yellow begins at the exact same position, which is what gives you a hard edge instead of a gradient fade. Change 45deg to -45deg to flip the stripe direction, or add a background-position transition if you want the stripes to march.

Dots are the same idea with a radial gradient and an explicit tile size:

.hero {
  background-color: #0a0e1a;
  background-image: radial-gradient(#1e293b 2px, transparent 2px);
  background-size: 22px 22px;
}

The radial gradient draws one 2px dot; background-size: 22px 22px defines the tile, so the dot repeats every 22px in both directions. One common trap here: if you copy only the background-image line and forget background-size, the dot renders once at full element size instead of repeating. Tiled patterns need all the lines.

Where these patterns earn their keep

The most useful spots are section and card backgrounds that would otherwise read as flat. A SaaS hero in a single flat colour looks like an unfinished template; a low-contrast dot texture (background #0a0e1a, dots a hair lighter at #1e293b, 2px size, ~22px spacing) adds quiet depth without competing with the headline. A dashboard of white cards on a white page feels lifeless; a deep-navy blueprint grid behind the charts gives a technical backdrop, and one spacing slider controls the whole density.

Checkerboards are perfect for transparency previews — the grey grid image editors use to say "this area is empty" — and a conic-gradient version tiles cleanly at any size with no 16×16 PNG to ship. And because the output is plain background-image, it drops straight into a Tailwind arbitrary value like bg-[image:repeating-linear-gradient(...)] without a plugin or a new asset.

I stopped exporting tiles entirely

I rebuilt a marketing site's section backgrounds last month and went in expecting to keep two or three PNG tiles around for the "fancy" textures. I did not end up keeping any. Every backdrop I needed — a dotted hero, a blueprint grid behind a feature table, diagonal stripes on a maintenance banner — turned out to be three lines of gradient CSS. The page weight for backgrounds dropped from about 11KB of tile images to under 400 bytes of inline CSS, and dark mode became a colour-variable swap instead of a second set of exported assets. The part that sold me was theming: a designer asked to nudge the grid tighter and recolour it on a Friday, and it was a two-number edit instead of a round trip through an image editor.

Hand-tuning gradient stop positions by counting pixels gets tedious fast, which is exactly why a generator helps. The CSS Pattern Generator gives you live preview sliders for type, colour, feature size, spacing and angle, then hands you the exact background-* block with the px stops already computed — paste once and ship. When your background needs a smooth colour blend rather than a geometric repeat, reach for the gradient generator instead; the two cover most of what a page background ever needs.

Patterns built from gradients are smaller, sharper, and easier to theme than any image tile. Once the mental model clicks — hard stops make stripes, stacking and angling makes everything else — you stop reaching for the PNG export at all.


Made by Toolora · Updated 2026-06-13