A tiling SVG pattern is smaller than a PNG, sharp at any resolution and needs no extra request. Here is how to embed one properly, and the two rules that catch everybody.
A repeating background — dots, a grid, hairline stripes — is geometry, and geometry is what SVG is for. One 24-pixel tile covers a 5K hero at any zoom level without a second asset, and it usually weighs a few hundred bytes where the PNG equivalent needs several kilobytes and a 2x version alongside it.
Embedded as a data URI it also costs no extra HTTP request, which for something this small is most of the win. The SVG Pattern Generator produces both the tile and the ready-to-paste CSS line.
An SVG is text, so it can go into a data URI as-is — percent-encoded rather than Base64. That matters: Base64 inflates by about a third, while percent-encoded markup stays close to the original size and compresses far better over gzip or brotli, because it is still recognisably repetitive text.
The characters that actually have to be escaped in CSS are few. # is the one that bites everybody, because it starts a fragment identifier — an unescaped hex colour silently truncates the whole image. Escape it as %23, along with quotes and the angle brackets, and leave the rest legible.
This is the rule that surprises people who have used inline SVG: an SVG loaded as a background image cannot be styled by the page at all.
Referenced through url(), the file is an image, not part of your document. It gets no access to your CSS, so currentColor resolves to black rather than your text colour, custom properties do not reach it, and a :hover rule on the element cannot recolour it. Scripts inside it never run, and external fonts it references will not load.
So the colours must be baked into the markup before you encode it. To theme a pattern, generate one tile per theme — they are small enough that shipping two or three is cheaper than a single PNG. If you genuinely need to restyle the shape from CSS, it has to be inline SVG in the document, not a background.
A pattern is seamless when the tile meets itself on every edge. In practice that means anything crossing a boundary has to be drawn again on the opposite side: a dot centred on the corner needs to appear at all four corners, and a diagonal leaving the right edge must re-enter at the left at the same height.
Get this wrong and the seams are invisible at 100% and obvious the moment the element is wide. It is worth checking a generated tile against a large area rather than a small swatch.
The same embedding rules apply to one-off decorative shapes — the soft organic blobs behind hero headings, for instance, which the SVG Blob Generator builds as a single path you can drop straight into a component or use as a CSS mask.
One path, a few hundred bytes, sharp on every display, and no request. That is the whole case for vector backgrounds, and it holds for anything you can describe as geometry rather than photography.