The <img> tag
Every image starts with one tag and three things that matter: src (where the file is), alt (what it shows, for screen readers and broken images), and width/height (so the browser reserves space and the page doesn't jump while loading).
Tip: the image above sets width & height so its space is reserved before it loads.
HTML
<figure class="fig">
<img src="images/photo.jpg"
alt="A placeholder photograph"
width="600" height="400">
</figure>
CSS — keep it responsive & rounded
.fig { max-width: 360px; } /* cap the display size */
.fig img { width: 100%; /* fill the figure... */
height: auto; /* ...and preserve the ratio */
border-radius: 10px; }
alt. Describe the content for meaningful images, or use empty alt="" for purely decorative ones so screen readers skip them.Fit images into a box with object-fit
Photos come in all shapes. To force them into a consistent box without squishing, give the box a fixed size and use object-fit. Same image, same box, three behaviors:
cover — fills, cropscontain — fits, letterboxedfill — stretches (distorts)HTML
<figure><img class="fit-box fit-cover" src="images/photo.jpg" alt="Cover example"><figcaption><code>cover</code> — fills, crops</figcaption></figure> <figure><img class="fit-box fit-contain" src="images/photo.jpg" alt="Contain example"><figcaption><code>contain</code> — fits, letterboxed</figcaption></figure> <figure><img class="fit-box fit-fill" src="images/photo.jpg" alt="Fill example"><figcaption><code>fill</code> — stretches (distorts)</figcaption></figure>
CSS
.fit-box { width: 100%; height: 150px; border-radius: 10px; }
.fit-cover { object-fit: cover; } /* fills the box, crops overflow */
.fit-contain { object-fit: contain; background: #e9edf5; } /* fits whole image, letterboxed */
.fit-fill { object-fit: fill; } /* stretches to fit, distorts */
Make images fluid & responsive
The one CSS rule every image needs so it never overflows its container: let it shrink to fit while keeping its proportions.
CSS — the essential rule
img {
max-width: 100%;
height: auto;
}
Serve the right size with srcset
Don't ship a 2000px photo to a phone. srcset + sizes lets the browser pick the smallest file that still looks sharp, saving data and load time.
HTML — resolution switching
<img
src="photo-800.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 600px"
alt="Mountain lake at sunrise">
Art direction with <picture>
When you want a different crop on mobile vs desktop (not just a smaller file), use <picture> with <source> media queries.
HTML — different image per screen
<picture> <source media="(max-width: 600px)" srcset="hero-square.jpg"> <source media="(min-width: 601px)" srcset="hero-wide.jpg"> <img src="hero-wide.jpg" alt="Product on a desk"> </picture>
<img> or CSS background-image?
Use a real <img> when the image is content (a product, a person, a chart) — it can have alt text and be read by screen readers. Use a CSS background-image when it's decoration behind other content, like a hero banner with text on top.
Text over a hero image
A dark gradient keeps the text readable on any photo.
HTML
<div class="img-hero">
<div class="cap">
<h4>Text over a hero image</h4>
<p>A dark gradient keeps the text readable on any photo.</p>
</div>
</div>
CSS — background image + readable overlay
.img-hero {
position: relative;
min-height: 220px;
display: flex;
align-items: flex-end; /* push the caption to the bottom */
border-radius: 12px;
overflow: hidden;
background-image:
linear-gradient(to top, rgba(15,23,42,.85), rgba(15,23,42,.1)),
url('images/hero.jpg');
background-size: cover;
background-position: center;
}
.img-hero .cap { padding: 22px; color: #fff; }
Caption images with <figure>
When an image needs a visible caption, wrap it in <figure> and add a <figcaption>. It ties the caption to the image semantically — better than a loose paragraph underneath.
HTML
<figure> <img src="images/lake.jpg" alt="A placeholder landscape"> <figcaption>Figure 1. A caption describes or credits the image.</figcaption> </figure>
CSS
figure { max-width: 360px; margin: 0; }
figure img { width: 100%; height: auto; border-radius: 10px; }
figcaption { font-size: .85rem; color: #6b7280; margin-top: 8px; font-style: italic; }
Build an image grid / gallery
A responsive gallery is just CSS Grid plus object-fit: cover and aspect-ratio to keep every thumbnail a perfect square. Hover a tile for a gentle zoom.
HTML
<div class="gallery"> <a href="#"><img src="images/1.jpg" alt="Photo 1"></a> <a href="#"><img src="images/2.jpg" alt="Photo 2"></a> <a href="#"><img src="images/3.jpg" alt="Photo 3"></a> <a href="#"><img src="images/4.jpg" alt="Photo 4"></a> <a href="#"><img src="images/5.jpg" alt="Photo 5"></a> <a href="#"><img src="images/6.jpg" alt="Photo 6"></a> </div>
CSS
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.gallery img {
width: 100%;
aspect-ratio: 1 / 1; /* perfect squares */
object-fit: cover;
transition: transform .3s ease;
}
.gallery a:hover img { transform: scale(1.08); }
JavaScript — optional click-to-enlarge
// Turns the gallery into a simple lightbox: clicking any thumbnail
// opens the full image in a dark overlay; clicking again closes it.
// Add this overlay to your <body>:
// <div id="zoom" style="display:none;position:fixed;inset:0;
// background:rgba(0,0,0,.85);align-items:center;justify-content:center;
// z-index:9999;cursor:zoom-out;">
// <img alt="" style="max-width:90%;max-height:88%;border-radius:10px;">
// </div>
const zoom = document.getElementById('zoom');
const zoomImg = zoom.querySelector('img');
document.querySelectorAll('.gallery a').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
const img = a.querySelector('img');
zoomImg.src = img.src;
zoomImg.alt = img.alt;
zoom.style.display = 'flex';
});
});
zoom.addEventListener('click', () => { zoom.style.display = 'none'; });
document.addEventListener('keydown', e => {
if (e.key === 'Escape') zoom.style.display = 'none';
});
Style & effects with CSS
Once an image is on the page, CSS can round it, shadow it, recolor it, or filter it — no image editor required.
HTML
<div class="fx"> <figure><img class="rounded" src="images/face.jpg" alt="Circular crop example"><figcaption>border-radius:999px</figcaption></figure> <figure><img class="shadow" src="images/card.jpg" alt="Drop shadow example"><figcaption>box-shadow</figcaption></figure> <figure><img class="gray" src="images/photo.jpg" alt="Grayscale filter example"><figcaption>filter:grayscale(1)</figcaption></figure> <figure><img class="bright" src="images/photo.jpg" alt="Brightness and saturation filter example"><figcaption>filter:brightness/saturate</figcaption></figure> </div>
CSS
.fx { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px,1fr)); gap: 12px; }
.fx figure { text-align: center; }
.fx img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 10px; } /* base look */
.fx figcaption { font-size: .74rem; color: #6b7280; margin-top: 6px; font-family: 'SF Mono', Consolas, monospace; }
.rounded { border-radius: 999px; aspect-ratio: 1/1; object-fit: cover; } /* circle crop */
.shadow { box-shadow: 0 10px 24px rgba(31,36,51,.35); }
.gray { filter: grayscale(1); } /* black & white */
.bright { filter: brightness(1.15) saturate(1.25); }
Fast-loading, modern images
A few attributes keep image-heavy pages quick and smooth.
loading="lazy"
Off-screen images load only as the user scrolls near them.
width & height
Set them so the browser reserves space and the layout doesn't shift (CLS).
Modern formats
WebP/AVIF are far smaller than JPEG/PNG at the same quality.
decoding="async"
Lets the browser decode images without blocking the rest of the page.
HTML — a well-optimized image
<img src="photo.jpg" alt="Sunlit workspace" width="800" height="600" loading="lazy" decoding="async">
HTML — modern format with fallback
<picture> <source srcset="photo.avif" type="image/avif"> <source srcset="photo.webp" type="image/webp"> <img src="photo.jpg" alt="Sunlit workspace"> </picture>
CSS — pair with fluid sizing
img {
max-width: 100%;
height: auto; /* with the width/height attrs set, this avoids layout shift */
}
JavaScript — manual lazy-load with IntersectionObserver
// For browsers without native loading="lazy" support, or when you want
// custom load-in behavior, use IntersectionObserver. Mark images with
// data-src and a tiny placeholder src; swap them in when they scroll near.
// <img data-src="photo.jpg" src="placeholder.svg" alt="Sunlit workspace"
// class="lazy" width="800" height="600">
const lazyImages = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
img.classList.add('loaded');
obs.unobserve(img);
});
}, { rootMargin: '200px' }); /* start loading 200px before they enter view */
lazyImages.forEach(img => observer.observe(img));
CSS — fade in once loaded
img.lazy { opacity: 0; transition: opacity .4s ease; }
img.loaded { opacity: 1; }
Writing good alt text
Alt text is read aloud by screen readers and shown when an image fails to load. Describe the meaning, not the file.
Do
- Describe content & purpose:
alt="Bar chart: sales up 20% in Q3" - Use
alt=""for decorative images so they're skipped. - Keep it concise — a sentence, not a paragraph.
Don't
- Stuff keywords or filenames:
alt="IMG_2043.jpg" - Start with “Image of…” — screen readers already say that.
- Leave it off entirely on meaningful images.
HTML — meaningful vs decorative
<!-- meaningful image: describe it --> <img src="chart.png" alt="Bar chart: sales up 20% in Q3"> <!-- decorative image: empty alt so it's skipped --> <img src="divider.png" alt="">
Drop a screenshot into a mockup
A mockup frames a screenshot inside a device or browser window so a flat image looks like a real product. You can build a simple frame in pure CSS — the screenshot just sits inside with object-fit: cover.
HTML — a browser-window mockup
<div class="mock-browser">
<div class="mock-bar">
<div class="mock-dots">
<span style="background:#ff5f57"></span>
<span style="background:#febc2e"></span>
<span style="background:#28c840"></span>
</div>
<div class="mock-url">yoursite.com</div>
</div>
<img src="images/screenshot.jpg" alt="Homepage screenshot">
</div>
CSS
.mock-browser { max-width: 460px; border-radius: 12px; overflow: hidden;
border: 1px solid #d7dce5; box-shadow: 0 14px 30px rgba(31,36,51,.18); }
.mock-bar { display: flex; align-items: center; gap: 6px;
padding: 10px 12px; background: #eef1f6; }
.mock-dots span { width: 11px; height: 11px; border-radius: 50%; }
.mock-url { flex: 1; margin-left: 10px; background: #fff; border: 1px solid #e2e6ee;
border-radius: 6px; padding: 4px 10px; font-size: .72rem; color: #9aa3b2; }
.mock-browser img { width: 100%; aspect-ratio: 16/10; object-fit: cover; }
HTML — a phone mockup
<div class="mock-phone"> <img src="images/app-screen.jpg" alt="App screenshot"> </div>
CSS
.mock-phone { width: 168px; background: #1f2433; border: 8px solid #1f2433;
border-radius: 30px; overflow: hidden; position: relative;
box-shadow: 0 14px 30px rgba(31,36,51,.22); }
.mock-phone::before { /* the speaker notch */
content: ""; position: absolute; top: 9px; left: 50%;
transform: translateX(-50%); width: 46px; height: 6px;
border-radius: 6px; background: #0a0a0f; }
.mock-phone img { width: 100%; aspect-ratio: 9/19; object-fit: cover; }
Both frames sit side by side in a flex container: .mock-wrap { display: flex; gap: 24px; flex-wrap: wrap; }
A complete, working image gallery
The sections above teach one idea at a time. This block is the whole thing in two pieces — paste the HTML into your <body> and the CSS into your <head>, swap in your own image paths, and you have a responsive gallery of perfect-square thumbnails that zoom on hover. No libraries needed.
1. HTML — paste into your <body>
<div class="photo-gallery"> <a href="images/1.jpg"><img src="images/1.jpg" alt="Photo 1" loading="lazy"></a> <a href="images/2.jpg"><img src="images/2.jpg" alt="Photo 2" loading="lazy"></a> <a href="images/3.jpg"><img src="images/3.jpg" alt="Photo 3" loading="lazy"></a> <a href="images/4.jpg"><img src="images/4.jpg" alt="Photo 4" loading="lazy"></a> <a href="images/5.jpg"><img src="images/5.jpg" alt="Photo 5" loading="lazy"></a> <a href="images/6.jpg"><img src="images/6.jpg" alt="Photo 6" loading="lazy"></a> </div>
2. CSS — paste into your <head>
<style>
.photo-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.photo-gallery a {
display: block;
border-radius: 10px;
overflow: hidden; /* clips the zoom to rounded corners */
}
.photo-gallery img {
width: 100%;
aspect-ratio: 1 / 1; /* perfect squares */
object-fit: cover; /* fill the square without distorting */
transition: transform .3s ease;
}
.photo-gallery a:hover img {
transform: scale(1.08); /* gentle zoom on hover */
}
</style>Everything on one page
Here's a small but complete page that uses nearly every technique above — a photographer's portfolio. It has a background-image hero with overlay text, a captioned <figure>, an object-fit gallery that zooms on hover, a circular avatar, and loading="lazy" on every image. First the rendered page, then the code — same structure and class names, with the gallery condensed to two tiles for brevity.
The rendered page
Photography
Maya Lin
Landscapes & light, from around the world.
Recent work
About
Maya is a travel photographer chasing first light in quiet places. Prints available on request.
HTML
<header class="pp-hero">
<div class="pp-hero-cap">
<p class="pp-kicker">Photography</p>
<h2>Maya Lin</h2>
<p>Landscapes & light, from around the world.</p>
</div>
</header>
<main class="pp-main">
<!-- content image with a caption -->
<figure class="pp-feature">
<img src="images/feature.jpg" alt="Sunrise over a mountain lake" loading="lazy">
<figcaption>Sunrise over Moraine Lake — shot on a 35mm lens.</figcaption>
</figure>
<!-- object-fit gallery, zooms on hover -->
<h3 class="pp-h3">Recent work</h3>
<div class="pp-gallery">
<a href="#"><img src="images/1.jpg" alt="Forest trail in fog" loading="lazy"></a>
<a href="#"><img src="images/2.jpg" alt="Desert dunes at dusk" loading="lazy"></a>
<a href="#"><img src="images/3.jpg" alt="City skyline at night" loading="lazy"></a>
<a href="#"><img src="images/4.jpg" alt="Waves on a rocky shore" loading="lazy"></a>
<a href="#"><img src="images/5.jpg" alt="Snow-capped peaks" loading="lazy"></a>
<a href="#"><img src="images/6.jpg" alt="Field of wildflowers" loading="lazy"></a>
<a href="#"><img src="images/7.jpg" alt="Autumn leaves on a path" loading="lazy"></a>
<a href="#"><img src="images/8.jpg" alt="Calm harbor at dawn" loading="lazy"></a>
<a href="#"><img src="images/9.jpg" alt="Rolling green hills" loading="lazy"></a>
<a href="#"><img src="images/10.jpg" alt="Lighthouse on a cliff" loading="lazy"></a>
<a href="#"><img src="images/11.jpg" alt="Foggy pine forest" loading="lazy"></a>
<a href="#"><img src="images/12.jpg" alt="Starry night sky" loading="lazy"></a>
</div>
<!-- circular avatar -->
<section class="pp-about">
<img class="pp-avatar" src="images/portrait.jpg" alt="Portrait of Maya Lin" loading="lazy">
<div>
<h3 class="pp-h3">About</h3>
<p>Maya is a travel photographer chasing first light in quiet places. Prints available on request.</p>
</div>
</section>
</main>
<footer class="pp-foot">
<p>© 2026 Maya Lin Photography</p>
</footer>CSS
.pp-hero {
min-height: 240px;
display: flex;
align-items: flex-end; /* caption sits at the bottom */
background-image:
linear-gradient(to top, rgba(15,23,42,.88), rgba(15,23,42,.15)),
url('images/hero.jpg');
background-size: cover;
background-position: center;
}
.pp-hero-cap { padding: 24px; color: #fff; }
.pp-kicker { text-transform: uppercase; letter-spacing: .16em; font-size: .7rem; font-weight: 700; color: #93c5fd; margin: 0; }
.pp-hero-cap h2 { font-size: 1.8rem; font-weight: 800; margin: 4px 0; }
.pp-hero-cap p { color: #e5e7eb; margin: 0; font-size: .9rem; }
.pp-main { padding: 22px; }
.pp-h3 { font-size: 1.05rem; font-weight: 700; color: #111; margin: 18px 0 10px; }
/* content image + caption */
.pp-feature { margin: 0; }
.pp-feature img { width: 100%; height: auto; border-radius: 10px; }
.pp-feature figcaption { font-size: .82rem; color: #6b7280; font-style: italic; margin-top: 6px; }
/* object-fit gallery with hover zoom */
.pp-gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.pp-gallery a { display: block; border-radius: 10px; overflow: hidden; }
.pp-gallery img { width: 100%; aspect-ratio: 1/1; object-fit: cover;
transition: transform .3s ease; }
.pp-gallery a:hover img { transform: scale(1.08); }
/* circular avatar */
.pp-about { display: flex; gap: 16px; align-items: center;
margin-top: 18px; padding-top: 18px; border-top: 1px solid #eef0f4; }
.pp-about p { margin: 0; font-size: .9rem; }
.pp-avatar { width: 84px; height: 84px; border-radius: 999px; object-fit: cover; flex-shrink: 0; }
/* dark footer */
.pp-foot { background: #0f172a; text-align: center; padding: 14px; }
.pp-foot p { color: #94a3b8; margin: 0; font-size: .85rem; }background-image hero with a gradient overlay (§4), a captioned <figure> (§5), an object-fit: cover + aspect-ratio gallery (§6), a border-radius: 999px circular crop (§7), and loading="lazy" on every image (§8) — all in one page.The whole thing as one file
Everything above — a background-image hero, a captioned <figure>, an object-fit gallery that zooms on hover, CSS effects (circle crop, grayscale), a circular avatar, and loading="lazy" on every image — combined into one complete HTML file. Copy it into a new file called index.html, open it in a browser, and it just works (demo photos come from picsum.photos). Here's the live result, then the full code.
Live preview
Complete HTML — copy this whole file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maya Lin — Photo Gallery</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1f2433; line-height: 1.55; }
img { display: block; }
/* ===== Background-image hero with readable overlay ===== */
.hero {
min-height: 300px; display: flex; align-items: flex-end;
background-image:
linear-gradient(to top, rgba(15,23,42,.88), rgba(15,23,42,.15)),
url('https://picsum.photos/seed/galleryhero/1200/600');
background-size: cover; background-position: center;
}
.hero-cap { padding: 28px; color: #fff; }
.hero-cap .kicker { text-transform: uppercase; letter-spacing: .16em; font-size: .72rem; font-weight: 700; color: #93c5fd; }
.hero-cap h1 { font-size: 2.2rem; font-weight: 800; margin: 4px 0; }
.hero-cap p { font-size: .95rem; opacity: .92; }
main { max-width: 880px; margin: 0 auto; padding: 28px 22px 48px; }
h2.section-title { font-size: 1.25rem; font-weight: 700; margin: 28px 0 12px; }
/* ===== Captioned figure (content image) ===== */
.feature { margin: 0 0 8px; }
.feature img { width: 100%; height: auto; border-radius: 12px; }
.feature figcaption { font-size: .85rem; color: #6b7280; font-style: italic; margin-top: 8px; }
/* ===== object-fit gallery, zooms on hover ===== */
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.gallery a { display: block; border-radius: 10px; overflow: hidden; }
.gallery img {
width: 100%; aspect-ratio: 1 / 1; /* perfect squares */
object-fit: cover; /* fill without distorting */
transition: transform .3s ease;
}
.gallery a:hover img { transform: scale(1.08); }
/* ===== CSS effects strip ===== */
.fx { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 12px; }
.fx figure { text-align: center; }
.fx img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 10px; }
.fx .round { border-radius: 999px; aspect-ratio: 1 / 1; } /* circle crop */
.fx .shadow { box-shadow: 0 10px 24px rgba(31,36,51,.35); }
.fx .gray { filter: grayscale(1); } /* black & white */
.fx figcaption { font-size: .72rem; color: #6b7280; margin-top: 6px; font-family: 'SF Mono', Consolas, monospace; }
/* ===== Circular avatar in an about row ===== */
.about { display: flex; gap: 18px; align-items: center; margin-top: 28px; padding-top: 24px; border-top: 1px solid #eef0f4; }
.avatar { width: 88px; height: 88px; border-radius: 999px; object-fit: cover; flex-shrink: 0; }
.about p { font-size: .92rem; color: #4b5563; }
footer { background: #0f172a; text-align: center; padding: 18px; }
footer p { color: #94a3b8; font-size: .85rem; }
</style>
</head>
<body>
<!-- Decorative background-image hero with overlay text -->
<header class="hero">
<div class="hero-cap">
<p class="kicker">Photography</p>
<h1>Maya Lin</h1>
<p>Landscapes & light, from around the world.</p>
</div>
</header>
<main>
<!-- Content image with a caption -->
<figure class="feature">
<img src="https://picsum.photos/seed/feature/880/380"
alt="Sunrise over a mountain lake" loading="lazy">
<figcaption>Sunrise over Moraine Lake — shot on a 35mm lens.</figcaption>
</figure>
<!-- object-fit gallery, perfect squares that zoom on hover -->
<h2 class="section-title">Recent work</h2>
<div class="gallery">
<a href="#"><img src="https://picsum.photos/seed/gal1/300/300" alt="Forest trail in fog" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal2/300/300" alt="Desert dunes at dusk" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal3/300/300" alt="City skyline at night" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal4/300/300" alt="Waves on a rocky shore" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal5/300/300" alt="Snow-capped peaks" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal6/300/300" alt="Field of wildflowers" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal7/300/300" alt="Autumn leaves on a path" loading="lazy"></a>
<a href="#"><img src="https://picsum.photos/seed/gal8/300/300" alt="Calm harbor at dawn" loading="lazy"></a>
</div>
<!-- CSS effects: circle crop, drop shadow, grayscale -->
<h2 class="section-title">Styled with CSS</h2>
<div class="fx">
<figure><img class="round" src="https://picsum.photos/seed/fxa/200/200" alt="Circular crop example" loading="lazy"><figcaption>border-radius:999px</figcaption></figure>
<figure><img class="shadow" src="https://picsum.photos/seed/fxb/200/150" alt="Drop shadow example" loading="lazy"><figcaption>box-shadow</figcaption></figure>
<figure><img class="gray" src="https://picsum.photos/seed/fxc/200/150" alt="Grayscale filter example" loading="lazy"><figcaption>filter:grayscale</figcaption></figure>
</div>
<!-- Circular avatar (object-fit cover + border-radius) -->
<section class="about">
<img class="avatar" src="https://picsum.photos/seed/avatar/180/180"
alt="Portrait of Maya Lin" loading="lazy">
<div>
<h2 class="section-title" style="margin-top:0">About</h2>
<p>Maya is a travel photographer chasing first light in quiet places. Prints available on request.</p>
</div>
</section>
</main>
<footer>
<p>© 2026 Maya Lin Photography</p>
</footer>
</body>
</html>
The takeaway
Use a real <img> with alt and dimensions for content; background-image for decoration. Make images fluid with max-width:100%, fit them with object-fit:cover, serve the right size with srcset/<picture>, and keep pages fast with loading="lazy" and modern formats. CSS handles the rest — rounding, shadows, filters, and galleries.
Related pages & resources
Where to go next — related lessons in the Playground, plus trusted references and free tools for working with images.
In the Visual Playground
Links & Media
Linking, embedding, and media basics.
Gallery Pages
Full image-gallery layouts and patterns.
SVG Lab
Vector graphics & icons that scale crisply.
Video & Multimedia
Adding video and audio to a page.
Color
Build palettes that work with your imagery.
Accessibility
Alt text in context & inclusive design.
Mockup Lab
Frame screenshots in devices & browsers.
Device Mockup Lab
Phone, tablet & laptop frames.
References & free tools
- MDN — the <img> element full attribute reference
- web.dev — Learn Images responsive & performant images
- Squoosh compress & convert to WebP/AVIF in your browser
- TinyPNG fast PNG/JPEG compression
- Unsplash · Pexels free high-quality stock photos
- Lorem Picsum placeholder images (used in this page's demos)