Placeholder Image Generator

SVG placeholders with no request to anybody: a few hundred bytes in your own page instead of a round trip to a service that may not be there next year.

Enable JavaScript to customise; default output below.

Use the final dimensions, so the box reserves the right space and the layout does not move when the real image arrives.

Anything but custom works the height out from the width.

Pattern

Deliberately ugly. A placeholder that looks like a design is a placeholder that ships.

Empty uses the dimensions, which is the most useful thing it can say.

Live preview placeholder.svg.txt
Size                                                         800 × 450
Aspect ratio                                                 1.7778 (16:9)
Pattern                                                      diagonal
SVG size                                                     642 bytes

SVG
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="800" height="450" role="img" aria-label="800 × 450">
	<defs>
		<pattern id="ph-diagonal-800x450" width="16" height="16" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
			<line x1="0" y1="0" x2="0" y2="16" stroke="#8b93a7" stroke-width="6" />
		</pattern>
	</defs>
	<rect width="800" height="450" fill="#e6e9ef" />
	<rect width="800" height="450" fill="url(#ph-diagonal-800x450)" opacity="0.35" />
	<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="system-ui, sans-serif" font-size="56" fill="#8b93a7">800 × 450</text>
</svg>

As an img, with the dimensions that stop the layout jumping
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 450' width='800' height='450' role='img' aria-label='800 × 450'%3E %3Cdefs%3E %3Cpattern id='ph-diagonal-800x450' width='16' height='16' patternUnits='userSpaceOnUse' patternTransform='rotate(45)'%3E %3Cline x1='0' y1='0' x2='0' y2='16' stroke='%238b93a7' stroke-width='6' /%3E %3C/pattern%3E %3C/defs%3E %3Crect width='800' height='450' fill='%23e6e9ef' /%3E %3Crect width='800' height='450' fill='url(%23ph-diagonal-800x450)' opacity='0.35' /%3E %3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='system-ui, sans-serif' font-size='56' fill='%238b93a7'%3E800 × 450%3C/text%3E %3C/svg%3E" width="800" height="450" alt="" />

As a CSS background
.placeholder {
	aspect-ratio: 800 / 450;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 800 450' width='800' height='450' role='img' aria-label='800 × 450'%3E %3Cdefs%3E %3Cpattern id='ph-diagonal-800x450' width='16' height='16' patternUnits='userSpaceOnUse' patternTransform='rotate(45)'%3E %3Cline x1='0' y1='0' x2='0' y2='16' stroke='%238b93a7' stroke-width='6' /%3E %3C/pattern%3E %3C/defs%3E %3Crect width='800' height='450' fill='%23e6e9ef' /%3E %3Crect width='800' height='450' fill='url(%23ph-diagonal-800x450)' opacity='0.35' /%3E %3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='system-ui, sans-serif' font-size='56' fill='%238b93a7'%3E800 × 450%3C/text%3E %3C/svg%3E");
	background-size: cover;
}

642 bytes of markup, and no request to anybody. A placeholder from a
third-party service costs a DNS lookup, a connection and a round trip
per image, and hands the visitor's IP address and referrer to a company
they have never heard of. Several of the popular ones have also simply
stopped working, which takes every mock-up with them.

Use the final dimensions. 800 × 450 here means the box reserves the
right space, so nothing moves when the real image arrives. A placeholder
at a convenient size hides the layout shift until production, which is
where Cumulative Layout Shift gets measured.

The width and height attributes on the img are what give the browser the
aspect ratio before anything loads. They are not a styling instruction:
CSS can still size the element, and the attributes stop the reflow.

alt="" on a decorative placeholder is correct: it tells a screen reader
to skip an image that carries no information. When the real image goes
in, the alt text goes in with it, and an empty alt on a meaningful image
is a failure rather than a neatness.

The data URI is percent encoded rather than base64: smaller, and a
colour can be edited in place in the stylesheet. The hash in a colour
has to be written as %23 or the URL ends there, which is the usual
reason an inline SVG background silently fails.

Do not ship placeholders. They are for wireframes and for tests. A grey
box with its dimensions in the middle is unmistakable in a design review
and embarrassing in production, which is the argument for using
something this ugly on purpose.

Output is valid and updates as you type.

The usual placeholder is a URL to somebody else’s server. That means a DNS lookup, a connection and a round trip for every box on the page, the visitor’s IP address and referrer handed to a company they have never heard of, and a mock-up that breaks the day the service changes its URL scheme. Several of the popular ones have simply stopped working, taking every wireframe with them.

An SVG placeholder is a few hundred bytes of markup in your own page. It scales, it costs nothing, it works offline, and it can be a data URI in a stylesheet.

The one thing worth doing carefully is the size: a placeholder at the final dimensions reserves the right space, so nothing moves when the real image arrives.

How to use

  1. Put in the width and pick a ratio, or set the height yourself.
  2. Choose a pattern. They are deliberately ugly.
  3. Copy the SVG, the img tag, or the CSS background.

Example

800 × 450 with a diagonal pattern, 642 bytes:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 450" width="800" height="450" role="img" aria-label="800 × 450">
	<defs>
		<pattern id="ph-diagonal-800x450" width="16" height="16" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
			<line x1="0" y1="0" x2="0" y2="16" stroke="#8b93a7" stroke-width="6" />
		</pattern>
	</defs>
	<rect width="800" height="450" fill="#e6e9ef" />
	<rect width="800" height="450" fill="url(#ph-diagonal-800x450)" opacity="0.35" />
	<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="system-ui, sans-serif" font-size="56" fill="#8b93a7">800 × 450</text>
</svg>

and as an image, with the two attributes that stop the layout jumping:

<img src="data:image/svg+xml,%3Csvg…" width="800" height="450" alt="" />

Pitfalls

Use the final dimensions. A placeholder at a convenient size hides the layout shift until production, which is where Cumulative Layout Shift gets measured. If you do not know the final size, that is the thing to settle before the markup.

width and height on the img are not styling. They give the browser the aspect ratio before anything loads, and CSS can still size the element. Leaving them off is the single most common cause of a page that jumps.

alt="" is correct here. An empty alt tells a screen reader to skip an image that carries no information. When the real image goes in, real alt text goes in with it, and an empty alt on a meaningful image is a failure rather than a tidiness.

Encode the hash. In a data URI inside url(), a # starts a fragment, so fill="#e6e9ef" truncates the SVG at the colour and the background silently does not appear. The generated URI writes %23.

Ids have to be unique on the page. Two placeholders with the same pattern id and the second one wins for both. The generated id includes the pattern and the size, which helps and is not a guarantee.

Do not ship them. A grey box with its dimensions in the middle is unmistakable in a design review and embarrassing in production. That is the argument for using something this ugly on purpose: nobody mistakes it for a design.

A placeholder is not a loading state. For an image that is genuinely still loading, a blurred tiny version or a solid average colour reads better, and both need the real image to generate them.

Compatibility

Arithmetic and string handling in the browser: nothing is uploaded, nothing is stored, and no request is made anywhere.

SVG patterns, dominant-baseline and aspect-ratio are in every current browser. The patternTransform on the diagonal fill is the standard way to rotate a pattern without rotating the shape it fills.

The data URI is percent encoded rather than base64: smaller, and the colours stay readable and editable in the stylesheet. Base64 for an SVG is a habit from worse tooling.

Font size is derived from the smaller side, so the label stays proportionate on a banner and on a thumbnail, with a floor of 12 so it is legible on something tiny. font-family: system-ui means the label renders in whatever the operating system uses and needs no font to be available.

Rendering was checked in a browser: all four patterns come out at the requested size with the label centred, measured with getBBox rather than by eye.

Frequently asked questions

Can I use these in production as a fallback?
For a genuinely empty state, yes: an avatar with no photo, a product with no image. Label it with something better than the dimensions in that case, and keep the alt empty only if the surrounding text already says what is missing.
What about a raster placeholder?
An SVG is smaller and scales; a PNG placeholder is only worth it for a tool that cannot render SVG. If you need one, screenshot this.
How do I make a WordPress placeholder?
Paste the SVG into a Custom HTML block, or register it as an inline data URI in your theme’s CSS. WordPress will not let you upload an SVG without a plugin, and for a placeholder you do not need to.
Why is the pattern so faint?
The pattern layer sits at 35 percent opacity so the label stays readable over it. Change the opacity in the markup if you want it stronger.
Does it work in an email?
Data URI images are blocked by several email clients, including Gmail, so no. For email, a hosted PNG is the only reliable route.
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.