Building Header Images That Scale Across Every Screen
Wide and shallow on a laptop, tall and narrow on a phone, a header image has to work in very different boxes. Aspect ratios, focal points, text contrast and responsive markup decide whether it does.
A header image that spans the full width of the window lives a double life. On a large monitor it is a wide, shallow strip. On a phone held upright, the same space is narrow and comparatively tall. One image file scaled to both rarely works: the subject ends up tiny, cropped off, or hidden behind the headline. Getting it right takes a few CSS properties, the right markup and some care with text.
Set the shape with aspect-ratio
Giving the header a fixed pixel height is the classic mistake. A height that looks right on a laptop leaves a thin sliver on a wide monitor and swallows most of a phone screen.
The aspect-ratio property ties the height to the width instead:
.hero {
aspect-ratio: 16 / 9;
}
@media (max-width: 40rem) {
.hero {
aspect-ratio: 4 / 5;
}
}
On wide screens the header keeps a landscape shape. On narrow screens it switches to a taller one, leaving room for a headline and a button over the image instead of a cramped strip.
Knowing the shape in advance also prevents layout shift. When the browser can calculate the space an image will take, it reserves it before the file arrives, and the text below does not jump down when the image appears.
Full-screen headers
Some designs want the header to fill the whole first screen. On mobile, 100vh is measured as if the browser toolbars were hidden, so part of the header can end up underneath them. The small viewport unit avoids that:
.hero-full {
min-height: 100svh;
}
100svh uses the height available while the toolbars are showing, so the whole header fits on screen when the page first loads.
Keep the subject in frame
When an img fills a box of a different shape, object-fit: cover scales it to fill the box without distortion and crops whatever overflows. By default the crop is centered. If the subject sits off-center, object-position moves the anchor point:
.hero img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 70% 30%;
}
With 70% 30%, the point 70 percent across and 30 percent down the image stays aligned with the same point in the box. As the box narrows, the browser crops around that area, so a subject in the upper right part of the photo stays visible.
Change the crop with picture
Sometimes moving the anchor point is not enough. A wide landscape shot with a person at the far edge will never become a good portrait crop automatically. For that, prepare separate crops and let the picture element choose between them:
<picture>
<source media="(max-width: 40rem)" srcset="hero-portrait.jpg"
width="800" height="1000">
<img src="hero-wide.jpg" width="1600" height="900"
alt="Designer sketching a page layout at a wooden desk">
</picture>
The browser checks the source elements in order, uses the first one whose media condition matches, and falls back to the img. Only the chosen file is downloaded. The width and height on each source let the browser reserve the correct shape for each crop.
Make the text readable
Text over a photograph is hard to keep legible. The same white headline that is crisp over a dark area disappears over sky or a pale wall, and the light parts of the image move as the crop changes.
A gradient layer is the most dependable fix:
.hero-banner {
background-color: #1f2430;
background-image:
linear-gradient(to top, rgb(0 0 0 / 0.75), rgb(0 0 0 / 0.1)),
url("hero-wide.jpg");
background-size: cover;
background-position: center;
color: #fff;
}
The first background layer, the gradient, is drawn on top of the photo and darkens the lower part, where the text sits. The background-color matters too. On a slow connection the text appears before the image, and without a dark fallback, white text would sit on a white page until the photo loaded.
A subtle text-shadow can help at the edges, but it is no substitute for a proper backing. Always check the result against WCAG, which asks for a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. Measure at the brightest spot the text crosses, and check again at phone width, where the crop and the text position change.
Choose a format
Header images are often the largest single file on a page, which makes the format choice worthwhile. JPEG works everywhere. WebP and AVIF usually produce noticeably smaller files at similar visual quality, AVIF often more so, and both are supported in all current major browsers. Offering them through picture with a JPEG fallback costs nothing:
<picture>
<source type="image/avif" srcset="hero.avif">
<source type="image/webp" srcset="hero.webp">
<img src="hero.jpg" width="1600" height="900"
alt="Designer sketching a page layout at a wooden desk"
fetchpriority="high">
</picture>
The browser takes the first type it supports. Because a header image is usually the largest element visible when the page opens, fetchpriority="high" asks the browser to fetch it early. For the same reason, never add loading="lazy" to it.
Serve the right resolution
When every version shows the same crop and only the size differs, srcset and sizes on a plain img are enough:
<img src="hero-800.jpg"
srcset="hero-480.jpg 480w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w"
sizes="(max-width: 75rem) 100vw, 75rem"
width="1600" height="900"
alt="Designer sketching a page layout at a wooden desk">
The w descriptors state the pixel width of each file. The sizes attribute tells the browser how wide the image will be displayed: the full viewport up to 75rem, and a fixed 75rem beyond that. From those two pieces of information and the screen's pixel density, the browser picks a file.
For example, a phone with a viewport 400 pixels wide and a pixel density of 2 needs about 800 physical pixels, so it will typically take hero-800.jpg. A laptop showing the image 1,200 CSS pixels wide on a standard screen would pick hero-1200.jpg. Nobody downloads the largest file unless they can use it, and that, together with a sensible shape, a protected focal point and readable text, is what makes a header image work on every screen.
Frequently asked questions
Should a header image be a CSS background or an HTML img?
Use an img or picture element when the image carries meaning, needs alt text or should be chosen with srcset. A CSS background suits purely decorative images. An img in the HTML is also discovered earlier by the browser, which helps it load sooner.
How large should a header image file be?
There is no fixed number. Export at the largest size the layout actually displays, offer smaller versions through srcset, use WebP or AVIF where you can, and compare quality settings by eye until the file is as small as it can be without visible damage.
When should I use picture instead of srcset?
Use srcset and sizes when every version shows the same crop at different resolutions. Use picture with media conditions when the crop or composition must change, such as a tighter portrait crop for phones.
How do I keep text readable over a photo?
Place a gradient or semi-transparent layer behind the text, set a solid fallback background color, and check the contrast at the brightest part of the image the text crosses.