HTML & CSS

Images on the Web

From a single <img> tag to responsive, accessible, good-looking photos — here's how images actually get onto a web page, with live examples you can copy. (Demo photos come from picsum.photos.)

New to web images? Read the visual lesson

01 · The basics

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).

A placeholder photograph demonstrating the img tag

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; }
Always include alt. Describe the content for meaningful images, or use empty alt="" for purely decorative ones so screen readers skip them.
02 · Sizing

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:

Demo of object-fit cover
cover — fills, crops
Demo of object-fit contain
contain — fits, letterboxed
Demo of object-fit fill
fill — 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    */
cover is the everyday choice for thumbnails and cards — it keeps the image undistorted and just crops the overflow.
03 · Responsive

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>
04 · img vs background

<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; }
Layer a gradient over a photo (as above) so overlaid text stays legible no matter how bright the image is.
05 · Captions

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.

A placeholder landscape
Figure 1. A caption describes or credits the image.

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; }
06 · Galleries

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';
});
07 · Effects

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.

Circular crop example
border-radius:999px
Drop shadow example
box-shadow
Grayscale filter example
filter:grayscale(1)
Brightness and saturation filter example
filter:brightness/saturate

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); }
08 · Performance

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; }
09 · Accessibility

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="">
10 · Mockups

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.

yoursite.com
Website screenshot shown inside a browser-window mockup
App screenshot shown inside a phone mockup

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; }

Want ready-made frames? The Playground has full mockup tools — drop your screenshot into laptops, phones, and browser frames: Mockup Lab and Device Mockup Lab.
Copy & Paste

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>
All together

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.

Sunrise over a mountain lake
Sunrise over Moraine Lake — shot on a 35mm lens.

Recent work

Portrait of Maya Lin

About

Maya is a travel photographer chasing first light in quiet places. Prints available on request.

© 2026 Maya Lin Photography

HTML

<header class="pp-hero">
  <div class="pp-hero-cap">
    <p class="pp-kicker">Photography</p>
    <h2>Maya Lin</h2>
    <p>Landscapes &amp; 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>&copy; 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; }
Spot the techniques: a CSS 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.
Full 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 &amp; 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>&copy; 2026 Maya Lin Photography</p>
  </footer>

</body>
</html>
That's a real, standalone page. The live preview above is rendered from the exact code in the box — what you copy is precisely what you see.
11 · Recap

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.

12 · Keep Exploring

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

References & free tools