Most background patterns on product pages, such as faint grids behind a hero or dot fields behind a feature section, are not images. They are CSS gradients with hard color stops, drawn into a small tile that background-size repeats across the element. That means no extra request, no blurry edges on high-density screens, and colors you can change with a variable.
A grid from two linear gradients
.grid-bg {
background-color: #fff;
background-image:
linear-gradient(to right, #e5e7eb 1px, transparent 1px),
linear-gradient(to bottom, #e5e7eb 1px, transparent 1px);
background-size: 40px 40px;
}Each gradient draws one line. #e5e7eb 1px, transparent 1px puts two stops at the same position, which makes a hard edge: solid color for the first pixel, then transparent for the rest. The first gradient runs left to right, so it draws a vertical line on the left edge of each tile. The second runs top to bottom and draws a horizontal line along the top. background-size makes every tile 40px square, and the default background-repeat tiles them across the element.
- Cell size is background-size. Use two values (40px 24px) for rectangular cells.
- Line thickness is the stop position. 2px lines need #e5e7eb 2px, transparent 2px.
- background-position shifts the whole grid. Set it to center if the lines should be symmetric around the middle of the element instead of starting at its top-left corner.
- A major and minor grid is four layers with two sizes, for example 1px lines every 20px and darker lines every 100px. Give each layer its own entry in background-size.
Dots from a radial gradient
.dots-bg {
background-color: #fff;
background-image: radial-gradient(#94a3b8 1px, transparent 1.5px);
background-size: 16px 16px;
}
/* Staggered dots: the same layer twice, offset by half a tile */
.dots-staggered {
background-image:
radial-gradient(#94a3b8 1px, transparent 1.5px),
radial-gradient(#94a3b8 1px, transparent 1.5px);
background-size: 20px 20px;
background-position: 0 0, 10px 10px;
}A radial gradient is centered in its tile by default, so every tile gets one dot in the middle. With a hard stop (1px, transparent 1px) small dots can look jagged or vary in size because their edge falls between pixels; a half-pixel gap between the stops, as above, anti-aliases the edge. Dot radius and spacing interact: a 1px dot every 16px is a texture, a 2px dot every 12px is a visible pattern.
Stripes and checkerboards with repeating gradients
repeating-linear-gradient() repeats its color stops along the gradient line until it fills the element, so it does not need background-size. Each color can take two positions, which makes stripe widths easy to read.
/* 6px stripe, 8px gap, at 45 degrees */
.stripes-bg {
background-image: repeating-linear-gradient(
45deg,
#eef2f7 0 6px,
transparent 6px 14px
);
}
/* Checkerboard: two quarters of a conic gradient per tile */
.checker-bg {
background-image: repeating-conic-gradient(#eef2f7 0 25%, transparent 0 50%);
background-size: 24px 24px;
}Avoid making diagonal stripes by tiling a plain linear-gradient() with background-size. The stripes rarely line up across tile edges and you get visible seams. repeating-linear-gradient() has no tiles, so there are no seams. Hard diagonal edges can still look slightly stepped on low-density screens; very thin, high-contrast diagonals show it most.
Fading the pattern with mask-image
A pattern that stops abruptly at the edge of a section looks pasted on. A mask fades it out. mask-image uses the alpha of an image or gradient: where the mask is opaque the element shows, where it is transparent the element is hidden. Apply the mask to a separate layer that holds only the pattern. If you mask the section itself, the content fades with it.
<section class="hero">
<div class="hero-pattern" aria-hidden="true"></div>
<div class="hero-content">...</div>
</section>.hero {
position: relative;
background-color: #f8fafc;
}
.hero-pattern {
position: absolute;
inset: 0;
background-image:
linear-gradient(to right, #e2e8f0 1px, transparent 1px),
linear-gradient(to bottom, #e2e8f0 1px, transparent 1px);
background-size: 20px 30px;
/* Visible at the top center, fading out toward the sides and bottom */
mask-image: radial-gradient(ellipse 70% 60% at 50% 0%, #000 60%, transparent 100%);
pointer-events: none;
}
.hero-content {
position: relative; /* paints above the absolutely positioned pattern */
}The mask color does not matter, only its opacity, which is why #000 and transparent are the usual pair. A linear mask such as linear-gradient(to bottom, #000, transparent) fades the pattern toward the bottom edge. Unprefixed mask-image has worked across current browsers since late 2023; add -webkit-mask-image with the same value if you support older Safari or Chromium versions.
Layering a glow over the pattern
A soft colored glow gives a gray pattern some warmth and a focal point. It is one more radial-gradient() layer. Background layers paint first-on-top, so list the glow before the pattern if it should tint the lines, or after them if the lines should stay crisp over the color.
.glow-grid {
background-color: #0f172a;
background-image:
radial-gradient(circle 480px at 50% 0%, rgb(99 102 241 / 0.25), transparent),
linear-gradient(to right, rgb(148 163 184 / 0.12) 1px, transparent 1px),
linear-gradient(to bottom, rgb(148 163 184 / 0.12) 1px, transparent 1px);
/* One size per layer: the glow covers the element, the grid tiles */
background-size: 100% 100%, 32px 32px, 32px 32px;
background-repeat: no-repeat, repeat, repeat;
}background-size and background-repeat are lists that match the image layers in order. Forgetting this is the most common bug with glows: the glow gets the 32px tile size and turns into a grid of tiny glows.
Keeping patterns quiet behind text
A background pattern is decoration, and it should lose every contest with the content. Some practical limits:
- Keep line and dot colors close to the background. #e5e7eb lines on white are visible but faint; mid-gray lines start to compete with body text.
- Text crosses the lines, so check contrast against the line color too, not just the background. With faint lines this is rarely a problem; with a strong pattern, it is.
- Dense, high-contrast patterns behind small text are hard to read. If text sits in the middle of the pattern, fade the pattern out behind it with a mask, or put the text on a solid panel.
- Use transparent colors (rgb(148 163 184 / 0.12)) on dark backgrounds so one pattern works on several surface colors.
- Mark the pattern layer aria-hidden="true" if it is an element, and give it pointer-events: none so it never intercepts clicks.
For dark mode, put the colors in custom properties and switch them, rather than writing a second pattern.
.grid-bg {
--grid-line: #e5e7eb;
background-image:
linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
background-size: 40px 40px;
}
@media (prefers-color-scheme: dark) {
.grid-bg {
--grid-line: rgb(255 255 255 / 0.08);
}
}Writing patterns as Tailwind v4 classes
Tailwind has no pattern utilities, but arbitrary values cover them. bg-[...] sets background-image, bg-size-[...] sets background-size and mask-[...] sets mask-image. Inside the brackets, underscores stand for spaces; commas are kept as written.
<section class="relative bg-white">
<div
aria-hidden="true"
class="pointer-events-none absolute inset-0
bg-[linear-gradient(to_right,#e5e7eb_1px,transparent_1px),linear-gradient(to_bottom,#e5e7eb_1px,transparent_1px)]
bg-size-[40px_40px]
mask-[radial-gradient(ellipse_70%_60%_at_50%_0%,#000_60%,transparent_100%)]"
></div>
<div class="relative">...</div>
</section>Tailwind v4 also has mask utilities for common fades, such as mask-b-from-50% to fade toward the bottom, but the arbitrary form keeps the exact gradient you designed. If the same pattern appears in several places, a custom utility is easier to read than a long class:
@utility bg-grid {
background-image:
linear-gradient(to right, var(--color-gray-200) 1px, transparent 1px),
linear-gradient(to bottom, var(--color-gray-200) 1px, transparent 1px);
background-size: 40px 40px;
}Using GradientDeck pattern backgrounds
The pattern backgrounds page is a collection of 139 backgrounds: grids, dots, stripes, textures, and radial glows, filterable by Gradients, Geometric, Decorative and Effects. Open a preview to see a pattern full size along with its source. Copy code gives you a React snippet: a wrapper div with Tailwind classes (usually min-h-screen, w-full, relative, and the base color as an arbitrary value such as bg-[#f8fafc]), containing an absolute inset-0 z-0 div whose inline style object holds the gradient layers, sizes and any mask.
Two adjustments are usually needed after pasting. Replace min-h-screen if the pattern belongs to a section rather than the whole page. And give your content position: relative and a z-index above the pattern (relative z-10 in Tailwind), because the pattern layer is absolutely positioned with z-0 and would otherwise paint over non-positioned content. For plain HTML, change className to class and move the inline style values into a CSS rule, using kebab-case property names.
For soft color fields rather than repeating patterns, see Building mesh gradients from layered radial gradients.