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.

Grid
Dots
Stripes
Checkerboard

A grid from two linear gradients

css
.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

css
.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.

css
/* 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.

html
<section class="hero">
  <div class="hero-pattern" aria-hidden="true"></div>
  <div class="hero-content">...</div>
</section>
css
.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.

css
.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.

css
.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.

html
<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:

css
@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.

References

MDN: background-size

MDN: repeating-linear-gradient()

MDN: mask-image

Tailwind CSS: background-size

Tailwind CSS: mask-image