You already know the basics of putting a picture on a page — the <img> element, the src that points at a file, and the alt text that describes it. If any of that feels shaky, the links and images article is the place to start. This one picks up where that left off.
Because here’s the thing: a single <img src="photo.jpg"> is fine for a quick demo, but it’s not how real, fast, professional websites serve images. A modern page needs to send a small image to a phone on a slow connection and a sharp image to a 4K monitor — from the same line of HTML. It needs to not jolt the page around as pictures load. It needs to use file formats that are a fraction of the size of an old JPEG. This article is about all of that: the attributes and elements that turn a basic image into one that’s responsive, fast, and accessible.
Why one image is not enough
Imagine you have a beautiful hero photo that’s 2400 pixels wide. If you drop it onto the page with a plain <img>, every visitor downloads all 2400 pixels — including the person on a phone whose screen is only 400 pixels wide and on mobile data. They wait, they burn through their data plan, and they never even see the extra detail.
The opposite problem is just as real. Make the image small enough for phones, and it looks blurry and pixelated on a large, high-resolution desktop display.
The old answer was to pick a compromise size and hope. The modern answer is to provide several versions of the image and let the browser choose the best one for each visitor’s screen. That’s what responsive images are, and HTML gives you two tools for it: the srcset attribute and the <picture> element.
Responsive images with srcset
The srcset attribute lets you offer the browser a menu of image files at different sizes, and the browser picks whichever fits the user’s screen best. The simplest form lists files with a width descriptor — the actual pixel width of each file, written as a number followed by w:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1600.jpg 1600w"
alt="A mountain lake at sunrise"
/>
Here you’re telling the browser: “I have this picture at 400, 800, and 1600 pixels wide. Pick the right one.” A phone grabs the 400w file; a large desktop grabs the 1600w file. The plain src is still there as a fallback for very old browsers that don’t understand srcset — they just use that one.
But there’s a missing piece. The browser needs to know how big the image will actually be displayed on the page before it can choose — and it decides that very early, before the CSS layout is fully worked out. That’s what the sizes attribute is for:
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 50vw"
alt="A mountain lake at sunrise"
/>
The sizes attribute reads like a set of rules: “if the screen is 600px or narrower, this image fills the full viewport width (100vw); otherwise it takes up half the viewport (50vw).” The browser combines that with the descriptors in srcset and the screen’s pixel density to download exactly the file it needs — no more, no less.
Don't overthink sizes — start with 100vw
The sizes syntax looks intimidating, but you don’t need a PhD. If your image stretches to the full width of its column, sizes="100vw" is a perfectly good starting point. You only need the more complex media-condition rules when the displayed size of the image changes depending on the layout (full width on mobile, half width on desktop, and so on). Get the basics working first, then refine.
The simpler x descriptor
If your image is always displayed at a fixed size and you only care about serving sharper versions to high-density “Retina” screens, there’s a simpler form using pixel-density descriptors (1x, 2x):
<img
src="logo.png"
srcset="logo.png 1x, logo@2x.png 2x"
alt="ACY Partner Indonesia logo"
width="200"
height="60"
/>
This says: “use logo.png on normal screens, and the higher-resolution logo@2x.png on screens that pack in twice the pixels.” It’s perfect for logos, icons, and other elements whose display size never changes. No sizes attribute needed here.
Art direction with <picture>
srcset is about serving the same image at different resolutions. But sometimes you want to serve a genuinely different image depending on the screen — a wide, cinematic crop on desktop, but a tighter, taller crop on a phone so the subject stays visible. That’s called art direction, and it’s the job of the <picture> element.
<picture>
<source media="(max-width: 600px)" srcset="hero-portrait.jpg" />
<source media="(min-width: 601px)" srcset="hero-wide.jpg" />
<img src="hero-wide.jpg" alt="Our team at the launch event" />
</picture>
The <picture> element wraps several <source> options and one <img>. The browser walks through the <source> elements top to bottom, uses the first one whose media condition matches, and shows that image. The <img> at the bottom is mandatory — it’s the fallback and the element that actually gets displayed, so it carries the alt text.
The <img> inside <picture> is not optional
Beginners sometimes leave out the final <img> and wonder why nothing shows up. The <source> elements only suggest candidates — it’s the <img> that actually renders the picture and provides the alt text. Always include it, and always give it a sensible src as the universal fallback. Think of <source> as overrides and <img> as the default everyone falls back to.
Modern image formats: WebP and AVIF
For years the web ran on JPEG (for photos) and PNG (for graphics with sharp edges or transparency). They still work everywhere, but they’re old, and newer formats produce dramatically smaller files at the same quality. WebP is typically 25–35% smaller than an equivalent JPEG. AVIF is newer still and often smaller again. Smaller files mean faster pages and less data used — a real, measurable win.
The catch is that very old browsers don’t understand these formats. <picture> solves that elegantly: offer the modern format first, and let the browser fall back to a format it does understand.
<picture>
<source srcset="photo.avif" type="image/avif" />
<source srcset="photo.webp" type="image/webp" />
<img src="photo.jpg" alt="A mountain lake at sunrise" />
</picture>
The browser tries AVIF, then WebP, and if it understands neither, it falls back to the trusty JPEG in the <img>. Modern browsers get the small, fast file; everything else still works. You can combine this with srcset width descriptors on each <source> to get responsive and modern at once — that’s the gold standard for important images.
You don't have to hand-make every format
Generating AVIF, WebP, and JPEG versions of every image by hand would be tedious. In practice, your build tool, CMS, or image CDN does this automatically — you upload one high-quality original, and it produces the formats and sizes for you. The HTML above is what that tooling generates. Knowing the markup means you can read it, debug it, and write it by hand when you need to.
Performance: lazy loading and decoding
A page might have twenty images, but the visitor only sees the first one or two before they scroll. Downloading all twenty up front is wasteful. The loading="lazy" attribute tells the browser to hold off on loading an image until it’s about to scroll into view:
<img src="far-down-the-page.jpg" alt="A chart of quarterly results" loading="lazy" />
This is one of the easiest performance wins in all of HTML — a single attribute that can dramatically cut the data a page loads on first paint. Add it to images that start out below the fold.
Don't lazy-load your most important image
There’s one image you should not lazy-load: the big one at the top of the page that’s visible the moment it loads (often called the “hero” or LCP image). Lazy-loading it actually slows down how fast the page feels, because the browser deprioritizes the very thing the user is waiting to see. Rule of thumb: loading="lazy" for images below the fold, and leave the top, above-the-fold image to load normally (or even mark it fetchpriority="high").
There’s a companion attribute, decoding="async", that lets the browser decode the image off the main thread so it doesn’t block other work. It’s a small, safe optimization you can add to most non-critical images:
<img src="photo.jpg" alt="A description" loading="lazy" decoding="async" />
Preventing layout shift with width and height
Here’s a frustrating experience everyone has had: you start reading an article, an image finishes loading higher up, and suddenly the whole page jumps down and you lose your place. That jump is called layout shift, and it happens because the browser didn’t know how tall the image would be, so it reserved no space — then had to make room once the image arrived.
The fix is wonderfully simple: always give your images width and height attributes.
<img src="photo.jpg" alt="A description" width="800" height="600" />
These numbers don’t lock the image to that exact pixel size — your CSS can still make it responsive. What they do is tell the browser the image’s aspect ratio in advance, so it can reserve a correctly-shaped box and hold that space while the file downloads. The page never jumps. This is part of Google’s Core Web Vitals (specifically Cumulative Layout Shift), so it affects both user experience and SEO.
width/height attributes vs CSS sizing
Setting width="800" height="600" in HTML isn’t a conflict with sizing the image in CSS — they do different jobs. The attributes give the browser the aspect ratio so it can reserve space early; your CSS (like max-width: 100%; height: auto;) controls how big the image actually renders. Use both together: the attributes for stability, the CSS for responsiveness.
Figures and captions
When an image needs a caption — a photo credit, a description, a “Figure 1” label — don’t just stick a <p> underneath it and hope. HTML has a semantic pair built exactly for this: <figure> and <figcaption>.
<figure>
<img src="sales-chart.png" alt="Bar chart of monthly sales, steadily rising from January to June" />
<figcaption>Monthly sales for the first half of 2026.</figcaption>
</figure>
<figure> wraps a self-contained piece of content — an image, a chart, a code snippet, a quote — and <figcaption> gives it a caption that’s programmatically tied to it. This is better than a loose paragraph because assistive technology understands the relationship: the caption belongs to that figure. Note the division of labor: the alt describes the image for someone who can’t see it at all, while the figcaption is a visible caption for everyone. They serve different readers, so they usually say different things.
If you’d like a quick refresher on semantic, meaning-carrying elements like this, the semantic HTML article covers the broader idea.
A quick word on SVG
Not every image is a photo. For logos, icons, and simple illustrations, SVG is often the better choice. SVG images are made of vector shapes described in text, so they stay perfectly crisp at any size — there’s no blurriness when you zoom in, and one file works on every screen density. You can use an SVG just like any other image:
<img src="logo.svg" alt="ACY Partner Indonesia logo" width="160" height="48" />
For an icon that needs to change color with CSS or be animated, you can also paste the SVG markup directly into your HTML (inline SVG) — but for a standard logo or illustration, referencing the .svg file with <img> is simple and works great. The rule of thumb: photos and rich images → JPEG/WebP/AVIF; logos, icons, and flat graphics → SVG.
Accessible images, briefly
Everything you learned about alt text in the basics still applies and matters more than any of the fancy attributes above. A few in-depth points worth holding onto:
- Informative images get a real
altdescribing what they show:alt="A bar chart showing sales rising each month". - Decorative images that add nothing to meaning get an empty
alt="", which tells screen readers to skip them. An empty alt is very different from a missing one — missing alt makes a screen reader read the filename, which is gibberish. - Images of text should be avoided where you can use real text instead, but if you must (like a logo with a tagline), put the words in the
alt. - Complex images like detailed charts may need a longer description nearby in the page, with the
altgiving the short version and the surrounding text giving the detail.
Good alt text isn’t decoration — it’s the difference between a usable page and an unusable one for people who rely on screen readers.
Putting it together
Here’s what a single, well-built important image looks like with the techniques from this article combined — responsive, modern format, stable, accessible:
<picture>
<source
type="image/avif"
srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1600.avif 1600w"
sizes="100vw"
/>
<source
type="image/webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1600.webp 1600w"
sizes="100vw"
/>
<img
src="hero-800.jpg"
alt="ACY Partner Indonesia team at the 2026 launch event"
width="1600"
height="900"
/>
</picture>
You won’t write this by hand for every image — your tooling will — but now you can read it, understand every line, and fix it when something’s off.
Wrapping up
You’ve gone from “an image is just <img src>” to understanding how the web actually delivers pictures well:
srcsetoffers the browser multiple sizes of the same image; pair it withsizesso it picks the right one. Use the simplerxdescriptor for fixed-size images on high-density screens.<picture>with<source>handles art direction (a different crop per screen) and modern format fallbacks (AVIF/WebP with a JPEG safety net). Always keep the inner<img>.- WebP and AVIF are far smaller than JPEG/PNG at the same quality — use them with a fallback.
loading="lazy"defers off-screen images for free performance — but never lazy-load your hero image.widthandheightattributes reserve space and stop the page from jumping (layout shift), which helps both users and SEO.<figure>and<figcaption>give images proper, semantically-linked captions.- SVG is the right format for logos, icons, and flat graphics — crisp at any size.
alttext is still the most important thing of all: real descriptions for informative images, empty for decorative ones.
Master these and your images stop being a performance liability and become exactly what they should be: fast, sharp, and usable for everyone. Next up in this section, we move from still images to audio and video — embedding media that plays.