Build a Scroll-Driven Photo Gallery

Create a responsive photo gallery with CSS scroll-driven animations.

Introduction

30 Second Summary

Long pages of photos can feel flat when every image moves in perfect lockstep. A small difference in motion makes scrolling feel layered without taking control away from the visitor.

In this project, you will build a responsive photo gallery with CSS Grid. CSS scroll-driven animations create layered movement while unsupported browsers keep a finished static layout.

What You'll Build

Your finished gallery responds to a normal page scroll with the right photo column drifting faster while each image gently zooms into view.

By the end of this project, you'll have:

  • A responsive photo gallery that switches from two columns to one when you narrow the browser window.
  • A layered scrolling effect where the right column drifts at a different speed while the page scrolls normally.
  • A progressive fallback that keeps the gallery polished when reduced motion is enabled or scroll-driven animations are unsupported.
  • Secret Mission: Add a popover photo preview that opens each image for a closer look.

Are there any prerequisites?

You need basic HTML and CSS experience. You can complete everything with a text editor plus a modern browser.

Before We Start

A smooth gallery build depends on a short loop between editing and checking. You need a dependable editor plus a browser that can show each change.

This step prepares Visual Studio Code for editing. You will finish with the photo-gallery folder open in the editor.

In this step, get ready to:
  • Get Visual Studio Code ready for editing.
  • Confirm your browser is available for testing.
  • Prepare the local photo-gallery workspace.
Install and open your code editor

Visual Studio Code keeps your page files together while you build. First, check whether it is already installed.

  • Press Cmd+Space on macOS or the Windows key on Windows to open your search bar.
  • Type Visual Studio Code into the search bar.
  • Press Enter.

✔️ Visual Studio Code opens

Your editor is installed and ready for the project.

ⓧ Visual Studio Code isn't installed

The editor needs to be installed before you create the project workspace.

macOS

  • Visit the official Visual Studio Code download page.
  • Download the .dmg installer from the macOS section.
  • Open the downloaded .dmg file.
  • Drag Visual Studio Code.app into the Applications folder.
  • Press Cmd+Space to open Spotlight search.
  • Type Visual Studio Code into Spotlight search.
  • Press Enter.

Windows

  • Visit the official Visual Studio Code download page.
  • Download the User setup installer from the Windows section.
  • Open the downloaded installer.
  • Continue through the installer using its default selections.
  • Wait for the installation to finish.
  • Press the Windows key to open search.
  • Type Visual Studio Code into search.
  • Press Enter.

Visual Studio Code opens to its start screen. Your editing space is ready.

Still unable to open the editor?

  • On macOS, confirm that Visual Studio Code.app is inside the Applications folder.
  • On Windows, run the User setup installer again if search cannot find the editor.
  • Help me troubleshoot my Visual Studio Code installation.
Confirm your browser

A browser turns an HTML file into the gallery you can see. Confirming your browser now protects the edit-to-check loop you use throughout the project.

  • Press Cmd+Space on macOS or the Windows key on Windows to open your search bar.
  • Type the name of your preferred browser into the search bar.
  • Press Enter.

You should see a browser window with an address bar. This is where you will test the gallery after each visible change.

Create the project folder

A local project folder gives the gallery one fixed home on your computer. Visual Studio Code may show a Workspace Trust prompt when you open it.

  • Switch back to Visual Studio Code from earlier.
  • Click File in the top menu bar.
  • Click Open Folder....
  • Select Desktop in the folder dialog.

The folder dialog now points to your Desktop. This location keeps the project easy to find after you close the editor.

  • Click New Folder.
  • Type photo-gallery as the folder name.
  • Press Enter to create the folder.
  • Click Open on macOS or Select Folder on Windows.
  • Click Yes, I trust the authors if the Workspace Trust prompt appears.

Before you check, picture the folder name you expect to see in the Explorer sidebar.

  • Check the Explorer sidebar in Visual Studio Code.

You should see photo-gallery at the top of the Explorer sidebar. The folder is empty and ready for your gallery page.

Your editing workspace is ready. Next, you will build the static two-column gallery and open it in your browser.

Build the Static Photo Grid

Motion works best when the page underneath it already feels complete. This step creates the static foundation that later effects can enhance without controlling the page's real scrolling.

Your index.html page uses HTML to group the photos. CSS Grid places those groups side by side with no JavaScript dependency.

In this step, get ready to:
  • Create a standalone index page containing two photo groups.
  • Arrange the photo groups as a two-column CSS Grid.
  • Open the page locally to confirm the static layout.
Create the gallery page

A standalone HTML document keeps the whole gallery in one file. The markup separates the photos into two groups so each column can behave independently later.

  • Use the new-file control in your editor's file sidebar.
  • Name the file index.html.
  • Build the page structure by pasting this code into index.html.
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Drift Gallery</title>
  <style>
    /* Give the gallery a calm page canvas. */
    * { box-sizing: border-box; }
    body { margin: 0; background: #f4f0e8; color: #1f241f; font-family: Arial, sans-serif; }
  </style>
</head>
<body>
  <!-- Group the photos into two columns for independent styling. -->
  <main class="gallery">
    <section class="photo-column" aria-label="First photo column">
      <img src="https://picsum.photos/200/300?random=1" alt="Gallery photograph one">
      <img src="https://picsum.photos/seed/picsum/200/300" alt="Gallery photograph two">
      <img src="https://picsum.photos/200/300?random=2" alt="Gallery photograph three">
    </section>
    <section class="photo-column" aria-label="Second photo column">
      <img src="https://picsum.photos/200/300?random=2" alt="Gallery photograph four">
      <img src="https://picsum.photos/200/300?random=1" alt="Gallery photograph five">
      <img src="https://picsum.photos/seed/picsum/200/300" alt="Gallery photograph six">
    </section>
  </main>
</body>
</html>

What does this code do?

  • The document metadata helps the gallery use the available width when it opens in a browser.
  • The <main> element contains the complete gallery.
  • Each <section> groups three photos into one column.
  • The embedded style gives the page its background before the grid layout is added.
  • Save index.html.
  • Use your editor's file-reveal command to show the project folder in Finder on macOS or File Explorer on Windows.
  • Double-click index.html to load it in your browser.

You should see six photos split across two stacked groups. That first visible result confirms the standalone page loads before the grid styling begins.

Photos not loading?

  • Check your internet connection because the photo sources load from remote URLs.
  • Confirm the saved file is named index.html.
  • Compare every opening HTML tag with its closing tag.
  • help me troubleshoot why the images in my local HTML gallery are not loading.
Turn the gallery into two columns

The .gallery element becomes the grid container. Its two direct children become equal columns.

  • Switch back to the index.html tab in your editor.
  • Find the closing </style> tag.
  • Add the grid container styles directly above that tag by pasting this code:
    /* Arrange the two photo groups as equal grid columns. */
    .gallery {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 1.5rem;
      max-width: 1100px;
      margin: 0 auto;
      padding: 1.5rem;
    }

What does this CSS do?

  • The display property turns the gallery into a grid container.
  • The grid-template-columns property creates two columns with equal available width.
  • The gap property leaves a clear channel between the columns.
  • The width rules keep the gallery centered inside the browser window.
  • Save index.html.
  • Refresh the gallery in your browser.

You should see the two photo groups sitting side by side with space between them. The gallery now has the two-column structure needed for independent motion.

Still seeing stacked groups?

  • Confirm the new rule sits inside the existing <style> element.
  • Check that the selector starts with a period and reads .gallery.
  • Confirm the page contains class="gallery" on the <main> element.
  • help me troubleshoot why my CSS Grid gallery still appears as one column.
Polish the photo columns

Consistent crop sizes turn the source images into a finished gallery. The object-fit property preserves each image's proportions while filling its frame.

  • Return to the closing </style> tag in index.html.
  • Add the column styles directly above that tag by pasting this code:
    /* Keep each column vertical and crop every photo consistently. */
    .photo-column {
      display: grid;
      gap: 1.5rem;
    }

    .photo-column img {
      display: block;
      width: 100%;
      aspect-ratio: 3 / 4;
      object-fit: cover;
      border-radius: 1rem;
    }

How do the photo styles help?

  • Each .photo-column selector stacks its three images vertically.
  • The width property stretches every image across its column.
  • The aspect-ratio property gives every frame the same portrait shape.
  • The object-fit property crops the image to fill that frame without distortion.

Before you refresh, do you expect the photos to keep their original sizes or fill both grid columns?

  • Save index.html.
  • Refresh the gallery in your browser.

You should see six full-width portrait crops arranged across two even columns. You now have a finished static gallery that runs with no JavaScript.

Do the photos look uneven?

  • Check that width: 100% appears inside the .photo-column img rule.
  • Confirm aspect-ratio: 3 / 4 ends with a code semicolon.
  • Verify that all six images sit inside an element with class="photo-column".
  • help me troubleshoot uneven image sizes in my two-column CSS gallery.

✔️ Awesome, I've got everything!

  • Confirm index.html shows as saved in your editor.
  • Keep the browser tab open for the next gallery change.

ⓧ I'd like to double check the full code

Compare your index.html file with this complete static gallery.

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Drift Gallery</title>
  <style>
    /* Give the gallery a calm page canvas. */
    * { box-sizing: border-box; }
    body { margin: 0; background: #f4f0e8; color: #1f241f; font-family: Arial, sans-serif; }

    /* Arrange the two photo groups as equal grid columns. */
    .gallery {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 1.5rem;
      max-width: 1100px;
      margin: 0 auto;
      padding: 1.5rem;
    }

    /* Keep each column vertical and crop every photo consistently. */
    .photo-column {
      display: grid;
      gap: 1.5rem;
    }

    .photo-column img {
      display: block;
      width: 100%;
      aspect-ratio: 3 / 4;
      object-fit: cover;
      border-radius: 1rem;
    }
  </style>
</head>
<body>
  <!-- Group the photos into two columns for independent styling. -->
  <main class="gallery">
    <section class="photo-column" aria-label="First photo column">
      <img src="https://picsum.photos/200/300?random=1" alt="Gallery photograph one">
      <img src="https://picsum.photos/seed/picsum/200/300" alt="Gallery photograph two">
      <img src="https://picsum.photos/200/300?random=2" alt="Gallery photograph three">
    </section>
    <section class="photo-column" aria-label="Second photo column">
      <img src="https://picsum.photos/200/300?random=2" alt="Gallery photograph four">
      <img src="https://picsum.photos/200/300?random=1" alt="Gallery photograph five">
      <img src="https://picsum.photos/seed/picsum/200/300" alt="Gallery photograph six">
    </section>
  </main>
</body>
</html>

Your static gallery is complete. Next up, you will add a custom breakpoint so the layout adapts to a narrow browser window.

Make the Gallery Responsive

The static gallery from the last step fills a wide browser with two balanced CSS Grid columns. A narrow window squeezes those columns until the photos lose their impact.

This step uses a media query to change the gallery from two columns to one at a custom breakpoint.

The HTML stays unchanged. JavaScript remains out of the page.

In this step, get ready to:
  • Add a custom narrow-screen media query to the embedded CSS.
  • Change the gallery grid to one column at narrow widths.
  • Resize the browser to confirm both layouts work.
Add the narrow-screen override

A breakpoint is the width where a different layout rule takes over. The two-column rule remains the gallery's default on wider screens.

What is a media query?

A media query watches a browser condition such as its width. Its rules apply while that condition matches.

The gallery keeps its two-column CSS as the default. The narrow-screen rule overrides the column count.

  • Switch back to index.html in your code editor.
  • Locate the embedded CSS rule that creates the two-column gallery grid.
  • Add a custom width-based media query after the default gallery styles with a one-column override for the same gallery selector.
  • Save index.html.

Before you check, do you expect a wide browser to keep the original layout or use the narrow override?

  • Return to the browser tab from the previous step.
  • Refresh the gallery page.

At a wide width, you should still see two photo columns. This confirms the default gallery layout remains intact.

Did the wide layout change?

  • Confirm that the one-column override sits inside the narrow-screen media query.
  • Check that the closing braces keep the narrow-screen rule separate from the default gallery styles.
  • Make sure the media query targets the same gallery selector as the default grid rule.
  • help me find why my wide gallery changed after I added a narrow-screen media query.
Test the layout switch

A responsive gallery needs a clean result on both sides of its breakpoint. Resizing the browser lets you test that transition directly.

Before you resize the browser, where do you expect the photos from the right column to move when the breakpoint activates?

  • Drag the edge of the browser inward until the narrow-screen breakpoint activates.

You should see the photos stack into one continuous column. Each image should stay inside the browser window.

  • Drag the edge of the browser outward until the breakpoint no longer matches.

You should see the two-column layout return. The same page now responds to the available width.

That layout switch is your responsive foundation working. Your gallery now handles wide screens plus narrow screens without JavaScript.

Gallery not switching layouts?

  • Save index.html again before refreshing the browser.
  • Confirm that the media query appears after the default gallery rule.
  • Check that the override uses the gallery's existing selector.
  • help me troubleshoot my responsive gallery breakpoint.

✔️ Awesome, I've got everything!

Your saved index.html now shows two columns on wide screens plus one column on narrow screens.

ⓧ I'd like to double check the full code

Compare your cumulative index.html against these requirements.

  • Confirm that index.html remains a standalone page with embedded CSS.
  • Confirm that the gallery markup still contains two photo columns with multiple images.
  • Confirm that the default gallery layout uses two columns.
  • Confirm that a custom narrow-screen media query changes the gallery grid to one column.
  • Confirm that the page contains no JavaScript dependency.
  • Narrow the browser once more until the one-column layout is visible.

Your gallery now adapts before any movement is added. Next, you'll make the right column drift at a different speed as the page scrolls.

Animate the Right Column

Your responsive CSS Grid gallery now switches between two columns and one column. The photos still move past the viewport at the same rate.

A scroll-driven animation connects visual progress to the page's scroll position. You will use this technique to make the right column drift while the browser keeps control of normal document scrolling.

In this step, get ready to:
  • Define a vertical drift for the right photo column.
  • Connect the drift to normal document scrolling inside an @supports rule.
  • Scroll the page to compare the two column speeds.
Add the scroll-driven drift

Keyframes define how far the column travels. A scroll() timeline controls progress through those keyframes.

The @supports rule makes this a progressive enhancement. Browsers apply the movement only when they recognize the scroll timeline syntax.

  • Switch back to index.html in your code editor.
  • Locate the closing </style> tag beneath the narrow-screen media query.
  • Add the animation CSS immediately above that tag by pasting this code:
/* Move the right column farther upward as the page scrolls */
@keyframes drift-up {
  from {
    transform: translateY(12vh);
  }
  to {
    transform: translateY(-20vh);
  }
}

/* Enable the effect only when scroll timelines are supported */
@supports (animation-timeline: scroll()) {
  .photo-column-right {
    animation-name: drift-up;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll();
  }
}

What Does This Code Do?

  • The drift-up keyframes define the right column's vertical journey.
  • The from keyframe starts the right column 12vh below its normal position.
  • The to keyframe finishes the right column 20vh above its normal position.
  • The @supports condition limits the enhancement to browsers that recognize animation-timeline: scroll().
  • The linear timing keeps the drift proportional to scroll progress.
  • The scroll() timeline replaces clock time with the page's scroll position.
  • Save index.html.
  • Return to the browser from earlier.
  • Refresh the gallery page.

At the top of the page, you should see the right column begin slightly lower than the left column. The responsive grid remains intact.

Right Column Still Aligned?

  • Check that .photo-column-right matches the class on the right photo column in your gallery markup.
  • Confirm that the new CSS sits before the closing </style> tag.
  • Make sure your test browser supports CSS scroll-driven animations.
  • Help me debug a right column that is not using its scroll-driven animation.
Compare the column movement

The strongest check compares both columns during the same scroll gesture. Their relative positions should change as the timeline progresses.

Before you scroll, do you expect the two columns to keep the same vertical alignment?

  • Scroll to the top of the gallery.
  • Focus on one photo near the top of each column.
  • Scroll downward at a steady pace.

You will see the right column drift upward relative to the left column. The page continues to scroll normally.

That motion confirms the timeline is working. The right column now travels farther during the same page movement.

Why the Static Gallery Is Safe

A browser that does not recognize the feature condition ignores the animation block. Your existing responsive grid remains available as the finished static fallback.

The gallery needs no JavaScript to control scrolling. The visual effect stays separate from the page's core layout.

✔️ Awesome, I've got everything!

Great. Make sure index.html is saved with the working right-column drift.

ⓧ I'd like to double check the full code

Your existing gallery markup and responsive media query stay unchanged. Compare the animation CSS in index.html with this reference.

/* Move the right column farther upward as the page scrolls */
@keyframes drift-up {
  from {
    transform: translateY(12vh);
  }
  to {
    transform: translateY(-20vh);
  }
}

/* Enable the effect only when scroll timelines are supported */
@supports (animation-timeline: scroll()) {
  .photo-column-right {
    animation-name: drift-up;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: scroll();
  }
}

Your gallery now combines a responsive static layout with a supported scroll-driven drift. Next up, you will give each photo its own movement as it crosses the viewport.

Animate Photos in View

Your gallery already uses CSS to move the right column at a different rate. The photos inside each column still keep the same scale while they cross the screen.

This step gives every photo its own view progress timeline. Each image gently zooms out while drifting into its resting position.

In this step, get ready to:
  • Compare the current column motion with the static photos.
  • Attach a view-driven keyframe animation to every photo.
  • Scroll through the gallery to verify both motion layers.
Compare the photos before animation

The current animation tracks the page's total scroll progress. Every image inside a column moves as part of that column.

Before you scroll, which parts of the gallery do you expect to move independently? Hold that prediction in mind.

  • Switch back to the browser from the previous step.
  • Refresh the gallery page.
  • Scroll slowly from the top toward the middle of the gallery.

You can see the right column drift at a different rate. Each photo keeps a fixed scale within its moving column.

Add the view-driven photo animation

The view() timeline measures one element's journey across the viewport. This gives every photo independent animation progress.

  • Switch back to index.html in the code editor.
  • Find the closing brace of the existing @supports block that contains animation-timeline: scroll().
  • Add this feature query immediately after that closing brace:
@supports (animation-timeline: view()) {
  .photo > img {
    /* Give each image its own viewport-driven timeline. */
    animation-name: image-drift;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
}

What does this feature query do?

  • The @supports condition limits the enhancement to browsers that understand a view() timeline.
  • The .photo > img selector gives every gallery image its own animation.
  • The linear timing keeps the image movement tied evenly to scroll progress.
  • The cover range maps the animation across the photo's full journey through the viewport.
  • Add the matching keyframe block directly below the new feature query:
@keyframes image-drift {
  from {
    /* Begin enlarged and slightly above the resting position. */
    scale: 1.5;
    translate: 0 -60px;
  }
  to {
    /* Settle into the image's natural size and position. */
    scale: 1;
    translate: 0 0;
  }
}

How does the motion work?

  • The from keyframe enlarges the image to 1.5 while shifting it upward by -60px.
  • The to keyframe returns the image to its natural scale and position.
  • Scrolling backward reverses the same keyframe progress.

Before you refresh, how do you expect a photo to change while it crosses the viewport? Keep that prediction in mind.

  • Save index.html.
  • Switch back to the gallery in your browser.
  • Refresh the page.
  • Scroll slowly through several photos.

You should see each photo begin enlarged and slightly raised. It settles into its natural size as it travels across the viewport.

You have just added a second layer of motion while keeping normal page scrolling intact.

Photos staying still?

  • Check that the @supports (animation-timeline: view()) block remains inside the page's <style> element.
  • Check that every image is a direct child of an element with the .photo class.
  • Check that image-drift is spelled the same way in the animation rule and the keyframe name.
  • Help me diagnose why my photo view animations are not moving.
Verify both motion layers

The right-column animation responds to the page's overall scroll position. Each photo animation responds to that image's position inside the viewport.

Before the final check, do you expect the column drift and photo zoom to progress at the same rate? Watch for the difference as you scroll.

  • Scroll back to the top of the gallery.
  • Choose one photo in the right column to follow.
  • Scroll downward until that photo crosses the viewport.
  • Reverse the scroll direction while the photo remains visible.

You should see the entire right column continue its shared drift. The chosen photo also zooms and shifts according to its own position in view.

When you reverse direction, both effects reverse smoothly with your scrolling. Your gallery now combines column-level movement with per-photo motion.

✔️ Awesome, I've got everything!

Great work. Make sure index.html is saved with both the feature query and the matching keyframes.

ⓧ I'd like to double check the full code

Compare this complete view-animation addition with the matching section in index.html. Keep all earlier gallery code unchanged.

@supports (animation-timeline: view()) {
  .photo > img {
    /* Give each image its own viewport-driven timeline. */
    animation-name: image-drift;
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
}

@keyframes image-drift {
  from {
    /* Begin enlarged and slightly above the resting position. */
    scale: 1.5;
    translate: 0 -60px;
  }
  to {
    /* Settle into the image's natural size and position. */
    scale: 1;
    translate: 0 0;
  }
}

Your gallery now layers two scroll-driven effects without JavaScript. Next, you will make that movement respect reduced-motion preferences.

Respect Reduced Motion

Your gallery now has a drifting right column. Each photo also zooms as it crosses the viewport.

Some visitors ask their operating system to reduce movement. In this step, CSS responds to that preference so the finished gallery stays still.

In this step, get ready to:
  • Add a reduced-motion media query to the gallery.
  • Enable reduced motion in your system settings.
  • Confirm that the gallery animations stop while the responsive layout remains intact.
Add the reduced-motion override

The prefers-reduced-motion media feature detects a system request for less movement. The value reduce activates styles that remove non-essential animation.

  • In index.html, scroll to the end of the embedded CSS.
  • Paste this block immediately before </style>:
@media (prefers-reduced-motion: reduce) {
  /* Remove gallery motion when the system requests it. */
  * {
    animation: none !important;
  }
}

What Does This Code Do?

  • The @media (prefers-reduced-motion: reduce) condition activates when the system reports a reduced-motion preference.
  • The * selector covers every element in this single-page gallery.
  • The animation: none !important declaration stops the column drift and the per-photo movement.
  • The gallery grid remains unchanged because this override only disables animation.
  • Save index.html.
  • Switch back to the browser from earlier.
  • Refresh the page.

You should still see the complete responsive photo grid. This confirms that the new CSS has preserved the gallery layout.

Gallery Layout Missing?

  • Check that the media query sits inside the existing <style> element.
  • Check that the media query has two closing braces before </style>.
  • Compare the spelling of prefers-reduced-motion with the reference block.
  • Help me check my reduced-motion CSS.

✔️ Awesome, I've got everything!

Great. Your reduced-motion override is saved at the end of the embedded stylesheet.

ⓧ I'd like to double check the full code

At the end of the embedded stylesheet in index.html, your reduced-motion block should match this reference.

@media (prefers-reduced-motion: reduce) {
  /* Remove gallery motion when the system requests it. */
  * {
    animation: none !important;
  }
}
Enable reduced motion

Your browser reads the motion preference from your operating system. The setting lives in a different accessibility panel on each platform.

macOS

  • Press Cmd+Space to open the search bar.
  • Type System Settings into the search bar.
  • Press Enter.
  • Select Accessibility in the sidebar.
  • Select Motion.
  • Turn on Reduce motion.

The reduced-motion preference is now active. Your browser can pass that request to the gallery.

Windows

  • Press Windows+I to open Settings.

Windows uses one of two accessibility layouts. Choose the tab that matches the category shown in your Settings window.

Accessibility layout

  • Select Accessibility in the sidebar.
  • Select Visual effects.
  • Turn off Animation effects.

Windows now reports a preference for less motion to your browser.

Ease of Access layout

  • Select Ease of Access.
  • Select Display.
  • Turn off Show animations in Windows.

Windows now reports a preference for less motion to your browser.

Confirm the gallery stays still

Before you test it, do you expect either gallery animation to keep moving after the browser reads your new preference?

  • Switch back to the browser from earlier.
  • Refresh index.html.
  • Scroll from the top of the gallery to the bottom.

You should see the right column remain fixed within the grid. Each photo should keep a constant size and position as it crosses the viewport.

What Does This Prove?

The browser detected the system preference and activated your media query. The still responsive grid remains available without removing the original animations for visitors who have not requested reduced motion.

Gallery Still Moving?

  • Refresh the gallery after changing the system setting.
  • Confirm that the reduced-motion setting remains enabled in the accessibility panel.
  • Confirm that the media query appears after the existing gallery animation rules in index.html.
  • Help me diagnose why the gallery still moves.

Your gallery now follows each visitor's motion preference while keeping its responsive layout intact. Next up, you will confirm that a browser without scroll-driven animation support still shows a finished static gallery.

Verify the Static Fallback

Your gallery now respects reduced-motion preferences. The final compatibility question is what happens when a browser cannot understand the scroll timelines.

This is the last proof of progressive enhancement. The base grid must carry the finished design when the enhanced motion is unavailable.

In this step, get ready to:
  • Restore your usual motion preference for a clear comparison.
  • Confirm a second browser lacks scroll-timeline support.
  • Test the static gallery at multiple widths.
Restore motion for a clean comparison

Your reduced-motion setting keeps the supported browser still. A compatibility test needs the animated version as its comparison point.

  • Return to the system motion settings from the previous step.
  • Restore your usual motion preference.
  • Switch back to the modern browser from earlier.
  • Reload the local index.html page.
  • Scroll through the gallery.

You should see the right column drift at a different speed. Individual photos should zoom gently as they cross the viewport.

Good. The animated version is now your comparison point.

Check the second browser's support

Browser support changes over time. The browser console gives you a direct compatibility result before you judge the fallback.

Finding a suitable browser can be fiddly because recently updated browsers may already support the feature. Use an installed browser or browser version that you expect to lack scroll-driven animation support.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Search for an installed browser or browser version that you expect to lack scroll-driven animation support.
  • Press Enter to launch it.

The second browser is ready for the same local page.

  • Switch back to the modern browser from earlier.
  • Select the local page address in the address bar.
  • Copy the selected address.
  • Return to the second browser.
  • Paste the address into its address bar.
  • Press Enter to load the gallery.
  • Open the browser console from its developer tools menu.

Before you check, do you think this browser recognizes the scroll timeline used by your gallery?

  • Check the browser's CSS support by entering this expression in the console:
CSS.supports("animation-timeline", "scroll()")

What does this check do?

  • The CSS.supports() method checks whether the browser understands a specific CSS property-value pair.
  • The animation-timeline property is the feature being tested.
  • The scroll() value matches the timeline protected by your @supports block.

✔️ I see false

That is the compatibility state you need. The browser leaves the @supports block inactive.

ⓧ I see true

This browser understands the scroll timeline. Use another installed browser or browser version for the fallback check.

  • Close the current test tab.
  • Repeat the browser search with another installed browser version.
  • Repeat the support check until the console returns false.

Not getting a Boolean result?

  • Make sure you entered the expression in the browser console instead of the address bar.
  • Check that the quotation marks around both values are straight quotation marks.
  • Confirm that scroll() includes both parentheses.
  • help me check why my browser console is not returning a Boolean result for this CSS support test.
Verify the static fallback

The default grid lives outside the @supports block. An unsupported browser ignores the enhanced motion while retaining the responsive gallery.

Before you scroll, do you expect the page itself to keep scrolling normally when both timeline animations are inactive?

  • Close the developer tools panel.
  • Scroll from the top of the gallery to the bottom.

The page should scroll normally. The right column should remain fixed in the grid. Each photo should keep a steady size.

  • Resize the browser window to a narrow width.

You should see one clean photo column. The images should remain readable without overlaps.

  • Widen the browser window again.

You should see the two-column layout return. The gallery should remain polished without any animation.

You have now proved the fallback works. Visitors get a finished responsive gallery even when their browser cannot run the enhanced motion.

✔️ Awesome, I've got everything!

Your compatibility checks are complete. Keep index.html saved in the local project folder.

ⓧ I'd like to double check the full code

This verification step makes no edits to index.html. Your full code remains unchanged from the previous step.

The completed file contains the gallery markup with embedded CSS. Its default grid uses two columns on wide screens. Its narrow-screen media query switches the grid to one column.

An @supports block contains the scroll() timeline for the right column. It also contains the view() timelines for individual photos.

The prefers-reduced-motion media query neutralizes the motion. No JavaScript or build tooling is present.

Secret mission

Create a Progressive Enhancement Test Report

Turn your gallery checks into a four-state acceptance report. Test the animated layouts. Prove each static fallback still looks deliberate.

Clean Up Your Resources

Clean Up Your Resources

This project has no ongoing costs because everything lives in the local index.html file. Decide whether to keep the file, pause your work, or delete it.

Resources you used:

  • Your local index.html photo gallery file.

Keep everything running

No action needed. Choose this if you want to reuse the gallery as a template.

  • Keep index.html saved in the local project folder.
  • Open index.html in a browser whenever you want to view or share the gallery.

Pause - I'll come back to this later

Close the files you are viewing to pause your work. No server keeps running in the background.

  • Save index.html in your code editor.
  • Close the index.html editor tab.
  • Close the gallery's browser tab if it is still open.

Delete - I don't want to use this again

Remove the local gallery file when you no longer want to keep the project.

  • Locate index.html in your code editor's file sidebar.
  • Use the file sidebar's delete action to remove index.html.
  • Confirm the deletion if your editor asks.
  • Check the file sidebar. The index.html file should no longer be listed.

Nice Work!

Nice Work!

You did it! Your shareable photo gallery now pairs a responsive layout with CSS scroll-driven animations.

You've learned how to:

  • Build a responsive two-column gallery with CSS Grid. Use @custom-media to switch it to one column on narrow screens.
  • Animate the right photo column with animation-timeline: scroll(). Give each photo a gentle zoom effect with animation-timeline: view().
  • Respect reduced-motion preferences with prefers-reduced-motion. Preserve a polished static fallback in browsers without scroll-driven animation support.
  • Secret Mission: Extend the finished gallery through the optional challenge.

Ready to quiz yourself?