Placeholder images in HTML and CSS
Updated
A placeholder for an image does one of three jobs. It stands in for a picture you don't have yet, it holds the space while the real image loads, or it replaces an image that failed. This page shows the HTML and CSS for each job.
Reserve the space first
Every method below works better when the browser knows the image size before the file arrives. Put width and height attributes on every <img>:
<img src="photo.jpg" width="600" height="400" alt="A red bicycle by a wall">
With both attributes, the browser calculates the aspect ratio before the image loads and reserves that space. Text below the image then stays where it is when the image appears. Without them, the page jumps when the image arrives. This jump is called layout shift.
The attributes still work in a responsive layout. Browsers turn them into a default style of aspect-ratio: auto 600 / 400. Add this CSS so the image shrinks to fit its column and keeps its shape:
img {
max-width: 100%;
height: auto;
}
The auto part means the real image's own ratio takes over once it loads. So if the attributes are a little wrong, the image is not stretched.
A CSS box as an image placeholder
For a mockup, you often need a grey box where an image will go later. A <div> with a background colour and aspect-ratio does this without any image file:
<div class="img-placeholder">600 × 400</div>
.img-placeholder {
aspect-ratio: 3 / 2;
background: #e2e8f0;
color: #475569;
display: grid;
place-items: center;
font: 14px/1 system-ui, sans-serif;
}
A block element takes the width of its container, so aspect-ratio alone sets its height. Don't give the box a fixed height as well. The aspect-ratio property only has an effect when at least one of the two sizes is automatic.
Put the label in a plain <div> or <span>. A heading such as <h4> would add a fake heading to the page outline.
An SVG placeholder
An SVG placeholder scales to any size and needs no request to a server. You can write it inline in the HTML:
<svg width="600" height="400" viewBox="0 0 600 400" role="img" aria-label="Image coming soon">
<rect width="600" height="400" fill="#e2e8f0"/>
<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle"
fill="#475569" font-family="sans-serif" font-size="24">600 × 400</text>
</svg>
You can also put the same SVG inside the src of a normal <img> as a data URL:
<img width="600" height="400" alt=""
src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='600'%20height='400'%3E%3Crect%20width='100%25'%20height='100%25'%20fill='%23e2e8f0'/%3E%3C/svg%3E">
A data URL must percent-encode reserved characters and spaces. The common trap is the # in a colour. Write it as %23, or the browser reads the rest of the URL as a fragment and the image breaks. Use single quotes inside the SVG so they don't close the src attribute. The xmlns attribute is required when the SVG is loaded as an image.
A placeholder image URL
When you need many sizes, a placeholder image URL is quicker than writing SVG by hand. Lorem.space returns an SVG of the size and colours you put in the URL:
<img src="https://api.lorem.space/image?w=600&h=400&bg=cbd5e1&fg=334155&text=Hello"
width="600" height="400" alt="">
Leave out text and the image shows its size, such as 600×400. The same URL works in CSS too:
.hero {
background: url("https://api.lorem.space/image?w=1200&h=500") center / cover;
}
The placeholder image generator builds these URLs and the <img> tag for you. The placeholder image API page lists every parameter and its limits.
A fallback when the real image fails
The browser fires an error event when an image can't load. This happens when the URL is empty, the file is corrupted or the format is not supported. You can catch the event and swap in a placeholder:
<img src="/uploads/avatar-42.jpg" width="96" height="96" alt="Sara Khan"
onerror="this.onerror = null; this.src = 'https://api.lorem.space/image?w=96&h=96';">
This pattern has three pitfalls:
- If the fallback URL also fails, the handler runs again. The line
this.onerror = nullremoves the handler first, so it can't repeat. - Inline handlers such as
onerror="..."are blocked by a Content Security Policy that does not allow inline scripts. In that case, add the listener from a script file. - A listener added from a script can miss the event if the image failed before the script ran. Check
img.complete && img.naturalWidth === 0when you attach it to catch images that already failed.
Keep the real alt text on the fallback. A screen reader user still needs to know what the image was meant to show.
Loading placeholders
A loading placeholder fills the reserved space until the real image is ready. There are three common kinds.
Dominant colour
Give the image a background colour taken from the photo itself. The colour shows in the reserved box until the image paints over it:
<img src="beach.jpg" width="600" height="400" alt="A beach at sunset"
style="background-color: #c98b5a">
This is the lightest option. It costs a few bytes per image. You need to pick the colour ahead of time, with an image tool or a build step.
Blurred low-quality image (LQIP)
A low-quality image placeholder is a tiny version of the photo, often about 20 pixels wide. Put it behind the real image as a background. The browser stretches it to fill the box, so it looks blurred:
<div class="lqip" style="background-image: url(data:image/jpeg;base64,...)">
<img src="beach.jpg" width="600" height="400" alt="A beach at sunset">
</div>
.lqip { background-size: cover; }
.lqip img { display: block; max-width: 100%; height: auto; }
The reader sees the shape and colours of the photo straight away. The tiny file adds bytes to the page, and you need a step that makes it for each image. If the preview is too vague, it adds a jarring swap without much benefit.
Skeleton shimmer
A skeleton is a grey box with a light band that moves across it. It tells the reader that content is on its way. It is common in cards and feeds, where the content of each image is unknown in advance.
<div class="skeleton">
<img src="card.jpg" width="600" height="400" alt="Product photo"
onload="this.parentElement.classList.remove('skeleton')">
</div>
.skeleton {
background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s linear infinite;
}
.skeleton img { display: block; max-width: 100%; height: auto; }
@keyframes shimmer {
from { background-position: 100% 0; }
to { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
.skeleton { animation: none; }
}
The width and height attributes give the box its size, so the wrapper needs no height of its own. The onload handler removes the class, so the animation stops once the image is ready. The prefers-reduced-motion query turns the animation off for people who set their device to reduce motion.
Lazy loading and placeholders
The loading="lazy" attribute tells the browser to wait until the user scrolls near an image before loading it. Lazy images show their placeholder for longer, so the reserved size matters even more. An unloaded image without dimensions has a width and height of 0. The browser may then never load it, because it never comes into view.
<img src="gallery-7.jpg" width="600" height="400" alt="Harbour at night" loading="lazy">
Use loading="lazy" only for images below the first screen. Images that are visible when the page opens, and the main image in particular, should load normally. The browser only defers loading when JavaScript is turned on.
Which method to use
| Situation | Method |
|---|---|
| Any real image on a live page | width and height attributes, with height: auto in CSS |
| Mockup or prototype with no images yet | CSS box, SVG or a placeholder image URL |
| Many sizes for a design or test | Placeholder image URL |
| No network requests allowed | Inline SVG or data URL |
| User uploads or third-party images that may break | onerror fallback |
| Photo galleries and article images | Dominant colour or LQIP |
| Cards and feeds loaded by script | Skeleton shimmer |
Accessibility
- A placeholder that only shows a grey box or a size label is decoration. Give it
alt="". Screen readers then skip it. - With
alt="", browsers also hide the broken-image icon if the image fails to display. - Give an inline SVG placeholder
aria-hidden="true"if it is decoration. If it stands for real content, give itrole="img"and anaria-label, as in the example above. - When a placeholder stands in for a real image, keep the real image's
alttext.
Frequently asked questions
How do I make a placeholder for an image in HTML?
Use an <img> tag whose src is a placeholder image URL or an SVG data URL, and set its width and height. You can also use a <div> with a background colour and aspect-ratio in CSS.
How do I create an image placeholder with CSS only?
Give a <div> a background colour and an aspect-ratio such as 3 / 2. A block element takes the width of its container, so the aspect ratio sets its height.
How do I show a placeholder while an image is loading?
Set width and height on the image so its space is reserved. Then fill that space with a dominant background colour, a blurred tiny version of the photo, or a skeleton shimmer.
How do I show a placeholder if an image fails to load?
Add an onerror handler that sets this.onerror = null and then changes this.src to a placeholder URL. Clearing the handler stops it from running again if the placeholder also fails.
Do width and height attributes stop layout shift?
Yes. With both attributes, the browser calculates the aspect ratio and reserves the space before the image loads, so the content below it does not move.
What alt text should a placeholder image have?
A placeholder that is only decoration should have alt="", so screen readers skip it. A placeholder that stands in for a real image should keep the real image's alt text.