CSS3 & Responsive Design

Responsive Images & Media

25 min Lesson 35 of 60

The Problem with Images on the Web

Images are one of the biggest challenges in responsive web design. A single high-resolution image meant for a desktop monitor might be 2000px wide and weigh 500KB or more. When that same image is served to a phone with a 375px viewport on a slow cellular connection, the user downloads 500KB of data only to have the browser shrink it down to a fraction of its size. This wastes bandwidth, slows page loading, drains battery, and creates a poor user experience. Responsive images solve this problem by serving the right image for the right context -- smaller files for smaller screens, larger files for larger screens, and different crops for different layouts.

In this lesson, you will learn every technique available for making images and media responsive, from the simple CSS max-width rule to the powerful HTML srcset and <picture> elements, along with modern CSS properties like object-fit, object-position, and aspect-ratio.

Fluid Images with max-width: 100%

The simplest and most fundamental technique for responsive images is the fluid image rule. This single CSS declaration prevents images from overflowing their containers:

Example: Basic Fluid Images

img {
    max-width: 100%;
    height: auto;
}

/* Apply to all media elements for consistency */
img, video, embed, object, svg {
    max-width: 100%;
    height: auto;
}

How this works: max-width: 100% tells the image it can never be wider than its containing element. If the container is 320px on a phone, the image will be at most 320px wide. If the container is 800px on a desktop, the image will display at up to 800px (but never larger than its intrinsic width). The height: auto declaration ensures the image maintains its natural aspect ratio as it scales, preventing distortion.

Note: The max-width: 100% approach makes images scale down but never scale up. If an image is naturally 400px wide and the container is 800px, the image will display at 400px, not stretch to 800px. This prevents blurry upscaled images. If you want an image to always fill its container regardless of its natural size, use width: 100% instead, but be aware that small images will become pixelated when stretched.

Understanding object-fit

When you set explicit dimensions on an image (using width and height in CSS) that do not match the image's natural aspect ratio, the image will be stretched or squished. The object-fit property controls how the image content is resized to fit within those dimensions, similar to how background-size works for background images.

There are five values for object-fit:

fill (Default)

The image stretches to completely fill the element's content box, ignoring its aspect ratio. This is the default behavior and often results in distorted images.

Example: object-fit: fill

.avatar {
    width: 200px;
    height: 200px;
    object-fit: fill;  /* Default: stretches to fill, may distort */
}

/* A 400x300 image forced into a 200x200 box
   will be squished horizontally */

contain

The image scales to fit entirely within the element's content box while maintaining its aspect ratio. The entire image is visible, but there may be empty space (letterboxing) on the sides or top/bottom.

Example: object-fit: contain

.product-image {
    width: 300px;
    height: 300px;
    object-fit: contain;
    background-color: #f5f5f5;  /* Shows through empty space */
}

/* A 600x400 landscape image in a 300x300 box:
   image shrinks to 300x200, centered vertically
   with 50px of empty space above and below */

cover

The image scales to completely cover the element's content box while maintaining its aspect ratio. The image may be cropped on the sides or top/bottom, but there is no empty space. This is the most commonly used value for hero images, card thumbnails, and profile pictures.

Example: object-fit: cover

.hero-image {
    width: 100%;
    height: 400px;
    object-fit: cover;
}

.card-thumbnail {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

.avatar-circle {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
}

none

The image is not resized at all. It displays at its natural size, and the element's content box acts as a viewport into the image. If the image is larger than the element, it overflows and is cropped by the element's boundaries.

Example: object-fit: none

.image-preview {
    width: 300px;
    height: 200px;
    object-fit: none;
    overflow: hidden;
}

/* A 1200x800 image will display at its full 1200x800 size,
   but you only see the 300x200 area in the center */

scale-down

The image is compared at both none and contain sizes, and the smaller of the two is used. In practice, this means: if the image is larger than the element, it behaves like contain (scales down). If the image is smaller than the element, it behaves like none (stays at natural size). This is useful when you want images to shrink but never stretch larger than their natural size.

Example: object-fit: scale-down

.icon-display {
    width: 200px;
    height: 200px;
    object-fit: scale-down;
}

/* A 64x64 icon stays at 64x64 (like 'none')
   A 800x600 photo shrinks to fit (like 'contain') */

Example: Comparing All object-fit Values Side by Side

.comparison-box {
    width: 250px;
    height: 250px;
    border: 2px solid #ddd;
}

.box-fill      { object-fit: fill; }       /* Stretches, may distort */
.box-contain   { object-fit: contain; }    /* Fits inside, may letterbox */
.box-cover     { object-fit: cover; }      /* Fills completely, may crop */
.box-none      { object-fit: none; }       /* Natural size, may overflow */
.box-scale     { object-fit: scale-down; } /* Smaller of none/contain */

Controlling Position with object-position

When using object-fit: cover or object-fit: none, the image may be cropped. By default, the browser centers the image within the element. The object-position property lets you control which part of the image is visible. It works exactly like background-position and accepts the same values: keywords, percentages, and length values.

Example: object-position with cover

/* Default: center center -- crops equally from all sides */
.hero {
    width: 100%;
    height: 400px;
    object-fit: cover;
    object-position: center center;
}

/* Focus on the top of the image (useful for portraits) */
.portrait-hero {
    width: 100%;
    height: 300px;
    object-fit: cover;
    object-position: center top;
}

/* Focus on the left side of the image */
.left-focused {
    width: 100%;
    height: 300px;
    object-fit: cover;
    object-position: left center;
}

/* Precise positioning with percentages */
.custom-focus {
    width: 100%;
    height: 300px;
    object-fit: cover;
    object-position: 30% 20%;  /* 30% from left, 20% from top */
}

/* Pixel offset from top-left corner */
.pixel-offset {
    width: 200px;
    height: 200px;
    object-fit: none;
    object-position: -50px -30px;  /* Shift image left 50px and up 30px */
}
Pro Tip: When using object-fit: cover for profile photos or portraits, always set object-position: center top. This ensures the person's face remains visible even when the image is heavily cropped on mobile, since faces are almost always in the upper portion of portrait photos.

The aspect-ratio Property

The aspect-ratio property is a modern CSS addition that sets a preferred aspect ratio for an element. Before this property existed, developers used the "padding-top hack" (a percentage-based padding trick) to maintain aspect ratios. Now, you can simply declare the ratio directly.

Example: Using aspect-ratio

/* 16:9 widescreen ratio */
.video-container {
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #000;
}

/* Perfect square */
.square-card {
    width: 100%;
    aspect-ratio: 1 / 1;
}

/* 4:3 traditional photo ratio */
.photo-frame {
    width: 100%;
    aspect-ratio: 4 / 3;
}

/* Combined with object-fit for images */
.card-image {
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
}

/* Responsive: change ratio at different breakpoints */
.hero-banner {
    width: 100%;
    aspect-ratio: 1 / 1;   /* Square on mobile */
    object-fit: cover;
}

@media (min-width: 768px) {
    .hero-banner {
        aspect-ratio: 16 / 9;  /* Widescreen on desktop */
    }
}
Note: The aspect-ratio property sets a preferred ratio, but it can be overridden by explicit height and width declarations. If you set both width: 300px and height: 200px, the aspect-ratio is ignored because the dimensions are already fully defined. The property is most useful when only one dimension is set (like width: 100%) and you want the other to be calculated automatically.

Responsive Images with srcset and sizes

While CSS max-width: 100% makes an image visually responsive, it does not solve the performance problem: the browser still downloads the full-size file. The HTML srcset and sizes attributes allow you to provide multiple versions of the same image at different resolutions, and the browser chooses the most appropriate one based on the viewport size and device pixel ratio.

Width Descriptors (w)

The most common use of srcset is with width descriptors, which tell the browser the actual pixel width of each image file:

Example: srcset with Width Descriptors

<img
    src="photo-800.jpg"
    srcset="
        photo-400.jpg 400w,
        photo-800.jpg 800w,
        photo-1200.jpg 1200w,
        photo-1600.jpg 1600w
    "
    sizes="
        (max-width: 600px) 100vw,
        (max-width: 1200px) 50vw,
        33vw
    "
    alt="A mountain landscape at sunset"
    width="1600"
    height="900"
>

Let us break this down piece by piece:

  • src -- The fallback image for browsers that do not support srcset.
  • srcset -- A comma-separated list of image files with their widths. photo-400.jpg 400w tells the browser this file is 400 pixels wide.
  • sizes -- Tells the browser how wide the image will be displayed at each viewport size. Reading the sizes attribute: "if the viewport is at most 600px, the image takes up 100% of the viewport width. If the viewport is at most 1200px, the image takes up 50%. Otherwise, 33%."

The browser uses the sizes information combined with the device pixel ratio to calculate which file to download. On a phone with a 375px viewport and 2x display, the browser calculates: 375px * 2 = 750px needed, so it downloads photo-800.jpg (the closest match above 750px). On a desktop at 1440px viewport showing the image at 33vw: 1440 * 0.33 = 475px, so photo-800.jpg is sufficient for a 1x display, or photo-1200.jpg for a 2x display.

Pixel Density Descriptors (x)

For images that are always displayed at the same CSS size (like logos or icons), you can use pixel density descriptors instead of width descriptors:

Example: srcset with Pixel Density Descriptors

<img
    src="logo-1x.png"
    srcset="
        logo-1x.png 1x,
        logo-2x.png 2x,
        logo-3x.png 3x
    "
    alt="Company logo"
    width="200"
    height="60"
>

/* The browser picks the file matching the device pixel ratio:
   - Standard display (1x): downloads logo-1x.png (200px)
   - Retina display (2x): downloads logo-2x.png (400px)
   - High-DPI mobile (3x): downloads logo-3x.png (600px) */
Common Mistake: Do not use x descriptors and w descriptors in the same srcset attribute. They are mutually exclusive. Use w descriptors with sizes for flexible-width images, and x descriptors for fixed-size images. If you omit the sizes attribute when using w descriptors, the browser defaults to sizes="100vw", which may cause it to download larger images than necessary.

The picture Element for Art Direction

While srcset lets the browser choose between different resolutions of the same image, the <picture> element gives you complete control over which image file is loaded at each breakpoint. This is called art direction -- serving entirely different image crops or compositions for different screen sizes.

For example, a wide landscape photo works on desktop, but on mobile, you might want a tightly cropped version focused on the subject. With srcset alone, the browser just scales the same image. With <picture>, you can specify a different image entirely.

Example: Art Direction with picture

<picture>
    <!-- Mobile: tightly cropped portrait version -->
    <source
        media="(max-width: 599px)"
        srcset="hero-mobile.jpg"
    >

    <!-- Tablet: medium crop -->
    <source
        media="(min-width: 600px) and (max-width: 1023px)"
        srcset="hero-tablet.jpg"
    >

    <!-- Desktop: wide landscape version -->
    <source
        media="(min-width: 1024px)"
        srcset="hero-desktop.jpg"
    >

    <!-- Fallback for browsers that don't support picture -->
    <img src="hero-desktop.jpg" alt="Team working in a modern office" width="1600" height="900">
</picture>

Example: Art Direction Combined with srcset for Resolution Switching

<picture>
    <!-- Mobile: cropped version, multiple resolutions -->
    <source
        media="(max-width: 767px)"
        srcset="
            hero-mobile-400.jpg 400w,
            hero-mobile-800.jpg 800w
        "
        sizes="100vw"
    >

    <!-- Desktop: full version, multiple resolutions -->
    <source
        media="(min-width: 768px)"
        srcset="
            hero-desktop-800.jpg 800w,
            hero-desktop-1200.jpg 1200w,
            hero-desktop-1600.jpg 1600w
        "
        sizes="100vw"
    >

    <img src="hero-desktop-1200.jpg" alt="Panoramic city skyline" width="1600" height="600">
</picture>

Using picture for Format Selection

The <picture> element can also serve different image formats, allowing you to use modern formats like WebP or AVIF for browsers that support them, with JPEG or PNG fallbacks:

Example: Format Selection with picture

<picture>
    <!-- AVIF: best compression, newest format -->
    <source type="image/avif" srcset="photo.avif">

    <!-- WebP: great compression, wide support -->
    <source type="image/webp" srcset="photo.webp">

    <!-- JPEG: universal fallback -->
    <img src="photo.jpg" alt="Product showcase" width="800" height="600">
</picture>

<!-- Combined: format selection + resolution switching -->
<picture>
    <source
        type="image/avif"
        srcset="photo-400.avif 400w, photo-800.avif 800w, photo-1200.avif 1200w"
        sizes="(max-width: 768px) 100vw, 50vw"
    >
    <source
        type="image/webp"
        srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
        sizes="(max-width: 768px) 100vw, 50vw"
    >
    <img
        src="photo-800.jpg"
        srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1200.jpg 1200w"
        sizes="(max-width: 768px) 100vw, 50vw"
        alt="Product showcase"
        width="1200"
        height="800"
    >
</picture>

CSS Background Images at Breakpoints

For decorative images or design elements set via CSS background-image, you can use media queries to serve different images at different breakpoints:

Example: Responsive CSS Background Images

/* Mobile: small background image */
.hero-section {
    background-image: url('hero-mobile.jpg');
    background-size: cover;
    background-position: center;
    min-height: 300px;
}

/* Tablet: medium image */
@media (min-width: 768px) {
    .hero-section {
        background-image: url('hero-tablet.jpg');
        min-height: 450px;
    }
}

/* Desktop: full resolution image */
@media (min-width: 1200px) {
    .hero-section {
        background-image: url('hero-desktop.jpg');
        min-height: 600px;
    }
}

/* Retina/HiDPI displays: serve 2x images */
@media (min-width: 1200px) and (min-resolution: 2dppx) {
    .hero-section {
        background-image: url('hero-desktop-2x.jpg');
    }
}

/* Using image-set() for modern browsers */
.modern-hero {
    background-image: image-set(
        url('hero.avif') type('image/avif'),
        url('hero.webp') type('image/webp'),
        url('hero.jpg') type('image/jpeg')
    );
    background-size: cover;
    background-position: center;
}
Pro Tip: Only use CSS background images for purely decorative elements. If an image conveys meaningful content, use an <img> tag with proper alt text instead. Background images are invisible to screen readers and cannot have alt text, so using them for content images creates accessibility problems.

Responsive Video Embeds

Videos have a fixed aspect ratio (usually 16:9), but their containers may be fluid. The challenge is maintaining the video's aspect ratio as the container width changes. There are two approaches: the classic padding-top trick and the modern aspect-ratio property.

The Classic Padding-Top Trick

This technique exploits the fact that percentage-based padding is calculated relative to the element's width, not its height. For a 16:9 ratio, the padding-top is calculated as (9 / 16) * 100 = 56.25%:

Example: Responsive Video with Padding-Top Trick

/* HTML:
<div class="video-wrapper">
    <iframe src="https://www.youtube.com/embed/VIDEO_ID"
            allowfullscreen></iframe>
</div>
*/

.video-wrapper {
    position: relative;
    padding-top: 56.25%;  /* 16:9 ratio: (9/16)*100 */
    width: 100%;
    overflow: hidden;
}

.video-wrapper iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* For 4:3 ratio videos: (3/4)*100 = 75% */
.video-wrapper-4x3 {
    padding-top: 75%;
}

/* For 21:9 ultrawide: (9/21)*100 = 42.86% */
.video-wrapper-ultrawide {
    padding-top: 42.86%;
}

The Modern aspect-ratio Approach

The aspect-ratio property makes responsive video containers much simpler:

Example: Responsive Video with aspect-ratio

/* Modern approach: much cleaner */
.video-wrapper {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
}

.video-wrapper iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Even simpler with direct iframe styling */
iframe.responsive-video {
    width: 100%;
    aspect-ratio: 16 / 9;
    border: 0;
}

Responsive Iframes

Iframes for embedded content (maps, CodePen demos, social media posts) present the same aspect ratio challenge as videos. The same techniques apply:

Example: Responsive Iframes

/* Generic responsive iframe container */
.iframe-container {
    width: 100%;
    aspect-ratio: 16 / 9;
    position: relative;
}

.iframe-container iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Map embed: might want a different ratio on mobile vs desktop */
.map-container {
    width: 100%;
    aspect-ratio: 1 / 1;  /* Square on mobile */
}

@media (min-width: 768px) {
    .map-container {
        aspect-ratio: 16 / 9;  /* Widescreen on desktop */
    }
}

/* Full-width iframe that respects max-width */
.content-embed {
    width: 100%;
    max-width: 800px;
    margin: 0 auto;
    aspect-ratio: 4 / 3;
}

Lazy Loading Images

Lazy loading defers the loading of off-screen images until the user scrolls near them. This dramatically improves initial page load time because the browser only downloads the images that are actually visible. HTML provides native lazy loading with the loading attribute:

Example: Native Lazy Loading

<!-- Eager: loads immediately (default behavior) -->
<img src="hero.jpg" alt="Hero banner" loading="eager" width="1600" height="900">

<!-- Lazy: loads when user scrolls near -->
<img src="photo-1.jpg" alt="Gallery photo 1" loading="lazy" width="800" height="600">
<img src="photo-2.jpg" alt="Gallery photo 2" loading="lazy" width="800" height="600">
<img src="photo-3.jpg" alt="Gallery photo 3" loading="lazy" width="800" height="600">

<!-- Lazy loading with srcset -->
<img
    src="product-800.jpg"
    srcset="product-400.jpg 400w, product-800.jpg 800w, product-1200.jpg 1200w"
    sizes="(max-width: 768px) 100vw, 50vw"
    alt="Product photo"
    loading="lazy"
    width="1200"
    height="800"
>

<!-- Lazy loading iframes -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
        loading="lazy"
        width="560"
        height="315"
        allowfullscreen></iframe>
Common Mistake: Do not lazy-load images that are visible in the initial viewport (above the fold). The hero image, logo, and any content visible without scrolling should use loading="eager" (or simply omit the loading attribute, since eager is the default). Lazy loading above-the-fold images actually hurts performance because the browser delays loading content the user needs to see immediately. For critical above-the-fold images, also add fetchpriority="high" to tell the browser to prioritize them.

Example: Proper Loading Priority Strategy

<!-- Above the fold: load immediately with high priority -->
<img src="hero.jpg" alt="Hero" fetchpriority="high" width="1600" height="900">
<img src="logo.png" alt="Logo" fetchpriority="high" width="200" height="60">

<!-- Below the fold: lazy load -->
<img src="feature-1.jpg" alt="Feature 1" loading="lazy" width="600" height="400">
<img src="feature-2.jpg" alt="Feature 2" loading="lazy" width="600" height="400">
<img src="feature-3.jpg" alt="Feature 3" loading="lazy" width="600" height="400">

Image Performance: Modern Formats

Choosing the right image format has a massive impact on performance. Modern formats can reduce file sizes by 25-50% compared to traditional JPEG and PNG, without visible quality loss.

Format Comparison

  • JPEG -- The classic format for photographs. Good compression, universal support. Use for photos and complex images with many colors.
  • PNG -- Lossless format that supports transparency. Larger file sizes than JPEG. Use for graphics with sharp edges, text, logos, or when you need transparency.
  • WebP -- Google's modern format. 25-35% smaller than JPEG at equivalent quality. Supports transparency and animation. Has excellent browser support (over 97% globally).
  • AVIF -- The newest format, based on the AV1 video codec. 50% smaller than JPEG at equivalent quality. Supports transparency, HDR, and wide color gamut. Browser support is growing rapidly (over 92% globally) but encoding is slower.
  • SVG -- Vector format for icons, logos, and illustrations. Infinitely scalable without quality loss. Small file size for simple graphics. Not suitable for photographs.

Example: Serving Modern Formats with Fallback

<picture>
    <source type="image/avif" srcset="photo.avif">
    <source type="image/webp" srcset="photo.webp">
    <img src="photo.jpg" alt="Descriptive text" width="800" height="600">
</picture>

/* The browser checks sources in order:
   1. Can I display AVIF? Yes -> downloads photo.avif (smallest)
   2. Can I display WebP? Yes -> downloads photo.webp (smaller)
   3. Falls back to photo.jpg (largest, but universal support) */
Note: Always specify width and height attributes on <img> elements, even when using CSS for responsive sizing. These attributes allow the browser to calculate the image's aspect ratio before the image loads, preventing layout shifts (Cumulative Layout Shift, or CLS). This is a key Core Web Vitals metric that affects search rankings.

Responsive SVGs

SVG (Scalable Vector Graphics) is inherently responsive because it uses mathematical descriptions of shapes rather than pixels. However, there are specific techniques to ensure SVGs behave correctly across different viewport sizes.

Example: Responsive Inline SVG

<!-- Inline SVG: use viewBox, omit width/height for fluid scaling -->
<svg viewBox="0 0 200 100" role="img" aria-label="Company logo">
    <rect x="10" y="10" width="80" height="80" fill="#2563eb" />
    <text x="110" y="60" fill="#1f2937" font-size="24">Brand</text>
</svg>

<!-- CSS to control SVG size -->
<style>
svg {
    width: 100%;
    max-width: 300px;
    height: auto;
}
</style>

Example: SVG as img Tag

<!-- SVG used as a regular image -->
<img src="illustration.svg" alt="Technical illustration" width="600" height="400">

<style>
/* Responsive SVG images follow the same rules as raster images */
img[src$=".svg"] {
    max-width: 100%;
    height: auto;
}
</style>

Example: SVG That Changes at Breakpoints

/* Show different levels of detail at different sizes */
.logo-icon .detail-elements {
    display: none;  /* Hide details on mobile */
}

@media (min-width: 768px) {
    .logo-icon .detail-elements {
        display: block;  /* Show full detail on larger screens */
    }
}

/* Change SVG size with CSS */
.icon {
    width: 24px;
    height: 24px;
}

@media (min-width: 768px) {
    .icon {
        width: 32px;
        height: 32px;
    }
}

Practical Complete Example

Let us put everything together in a real-world responsive image gallery that uses all the techniques covered in this lesson:

Example: Complete Responsive Image Gallery

<!-- HTML Structure -->
<section class="gallery" aria-label="Photo gallery">
    <h2>Our Work</h2>
    <div class="gallery-grid">

        <!-- Full responsive image with art direction + format selection -->
        <figure class="gallery-item gallery-item--featured">
            <picture>
                <source
                    media="(max-width: 767px)"
                    type="image/avif"
                    srcset="project1-mobile.avif"
                >
                <source
                    media="(max-width: 767px)"
                    type="image/webp"
                    srcset="project1-mobile.webp"
                >
                <source
                    media="(max-width: 767px)"
                    srcset="project1-mobile.jpg"
                >
                <source
                    media="(min-width: 768px)"
                    type="image/avif"
                    srcset="project1-desktop.avif"
                >
                <source
                    media="(min-width: 768px)"
                    type="image/webp"
                    srcset="project1-desktop.webp"
                >
                <img
                    src="project1-desktop.jpg"
                    alt="Modern e-commerce website design"
                    loading="eager"
                    fetchpriority="high"
                    width="1200"
                    height="800"
                >
            </picture>
            <figcaption>E-Commerce Redesign</figcaption>
        </figure>

        <!-- Standard lazy-loaded gallery items -->
        <figure class="gallery-item">
            <img
                src="project2-800.jpg"
                srcset="project2-400.jpg 400w, project2-800.jpg 800w"
                sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
                alt="Mobile banking application interface"
                loading="lazy"
                width="800"
                height="600"
            >
            <figcaption>Banking App</figcaption>
        </figure>

    </div>
</section>

<style>
/* Gallery grid: responsive without media queries */
.gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    padding: 16px;
}

/* Featured item spans full width on all screens */
.gallery-item--featured {
    grid-column: 1 / -1;
}

/* Consistent image sizing within cards */
.gallery-item img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 8px;
    display: block;
}

.gallery-item--featured img {
    aspect-ratio: 16 / 9;
}

/* Responsive figure styling */
.gallery-item {
    margin: 0;
}

.gallery-item figcaption {
    padding: 8px 0;
    font-weight: 600;
}

@media (min-width: 768px) {
    .gallery-grid {
        gap: 24px;
        padding: 24px;
    }
}
</style>
Pro Tip: For a real production site, automate image generation. Tools like Sharp (Node.js), Squoosh CLI, or image CDNs like Cloudinary and imgix can automatically generate multiple sizes and formats from a single source image. Manually creating 4 sizes in 3 formats for every image (12 files per image) is not sustainable -- automation is essential.

Practice Exercise

Build a responsive portfolio page that showcases your image and media skills. The page must include: (1) A hero section with a full-width background image that serves different crops on mobile versus desktop using the <picture> element. (2) A profile photo using object-fit: cover and object-position: center top inside a circular container. (3) A project gallery with at least 4 images using srcset and sizes for resolution switching, aspect-ratio for consistent card sizing, and loading="lazy" for images below the fold. (4) An embedded responsive YouTube video using the aspect-ratio property. (5) At least one SVG logo or icon that scales responsively. For all images, provide WebP versions alongside JPEG using the <picture> element for format selection. Verify that the hero image loads eagerly with fetchpriority="high", while gallery images are lazy loaded. Use browser DevTools Network tab to confirm that mobile devices download smaller image files than desktop devices.