Stagger Grid Animations by Position

Build a responsive CSS grid that calculates coordinates and staggers on reflow.

Introduction

30 Second Summary

Responsive layouts rearrange themselves whenever the browser gets narrower. The visual order changes, but animation timing often stays frozen in the old layout.

In this project, you will build a responsive CSS Grid in a single HTML file. Each item uses a scroll-driven animation to calculate its row and column before timing its own entrance.

What You'll Build

As you resize the browser, every tile updates its displayed row and column before the reflowed grid animates in a fresh diagonal wave.

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

  • A responsive coordinate display where each grid item reveals its current row and column.
  • Accurate starting coordinates for items that span extra grid tracks.
  • A diagonal entrance stagger that recalculates whenever the browser width changes.
  • Secret Mission: An optional challenge to push your CSS skills further.

Are there any prerequisites?

You need a text editor plus a current Chrome or Edge browser because the full feature set works there today. The project runs locally in one file at no cost.

Before We Start

This project depends on modern CSS behavior that some browsers do not fully support. A mismatched browser can make correct code look broken.

You will prepare a code editor for one HTML file. You will also prepare Google Chrome for testing.

In this step, get ready to:
  • Install Visual Studio Code as your code editor.
  • Update Google Chrome for testing.
  • Create the grid-row-column-animation folder on your Desktop.
Prepare your code editor

Visual Studio Code gives you one place to write the page. Its file explorer keeps the complete demo visible while you work.

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

✔️ Visual Studio Code opens

Great, Visual Studio Code is ready for your single-file project.

ⓧ Visual Studio Code is not installed

The download table is a little fiddly because it lists several processor types. Choose the instructions for your operating system below.

macOS

  • Visit the official Visual Studio Code download page.
  • Select the Universal download in the .dmg row.
  • Open the downloaded .dmg file.
  • Drag Visual Studio Code.app into the Applications folder.
  • Press Cmd+Space to open Spotlight.
  • Type Visual Studio Code into Spotlight.
  • Press Enter to open the editor.

Windows

  • Visit the official Visual Studio Code download page.
  • Select the User Installer for your processor.
  • Open the downloaded installer.
  • Allow the installer to make changes if Windows asks for permission.
  • Follow the installation prompts.
  • Press the Windows key to open search.
  • Type Visual Studio Code into search.
  • Press Enter to open the editor.

Having trouble opening the editor?

  • Confirm that you downloaded the installer for your operating system.
  • Close any incomplete installer before downloading a fresh copy.

Get help with opening Visual Studio Code after installation.

Prepare Chrome for testing

The grid measurement technique relies on newer browser features. Chrome gives you the support needed to see the coordinates update when the layout reflows.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Google Chrome into the search bar.
  • Press Enter to open the browser.

✔️ Google Chrome opens

Chrome is installed. You can now check that it has the latest available update.

ⓧ Google Chrome is not installed

macOS

macOS may request your administrator password during this installation. The prompt authorizes Chrome to copy into the Applications folder.

  • Visit the official Google Chrome download page.
  • Select Download Chrome.
  • Open googlechrome.dmg from your Downloads folder.
  • Drag Chrome into the Applications folder.
  • Press Cmd+Space to open Spotlight.
  • Type Google Chrome into Spotlight.
  • Press Enter to open the browser.

Windows

Windows may request permission before the installer runs. This allows the installer to add Chrome to your computer.

  • Visit the official Google Chrome download page.
  • Select Download Chrome.
  • Open the downloaded installer.
  • Select Yes if Windows asks for permission.
  • Follow the installation instructions.
  • Press the Windows key to open search.
  • Type Google Chrome into search.
  • Press Enter to open the browser.
  • Click the More button in the top right corner of Chrome.
  • Select Help.
  • Select About Google Chrome.
  • Select Relaunch if Chrome finishes an update.

You should see confirmation that Chrome is using the latest available version. That gives your CSS experiment the browser support it needs.

Chrome not opening or updating?

  • Check that your internet connection works before downloading the installer again.
  • Close Chrome before reopening it after an update.

Get help with installing or updating Google Chrome.

Create your project folder

A dedicated folder keeps the demo self-contained. It also gives Visual Studio Code a clear place for the upcoming index.html file.

  • Switch back to Visual Studio Code.
  • Select File from the menu bar.
  • Select Open Folder....
  • Navigate to your Desktop in the folder picker.
  • Use the new-folder control to create grid-row-column-animation.
  • Select the grid-row-column-animation folder.
  • Confirm your selection in the folder picker.
  • Confirm that you trust the folder if Visual Studio Code asks.

You should see GRID-ROW-COLUMN-ANIMATION in the Explorer sidebar. The folder is empty because the next step adds the page.

Folder missing from the Explorer?

  • Select File from the Visual Studio Code menu bar.
  • Select Open Folder... again.
  • Choose grid-row-column-animation from your Desktop.

Get help with opening the project folder in Visual Studio Code.

Before you make the final check, what should the editor and browser show if your setup is complete?

  • Look at the Explorer sidebar in Visual Studio Code.
  • Switch to Google Chrome from your Dock or taskbar.

You should see the empty grid-row-column-animation folder in Visual Studio Code. You should also see an updated Chrome window ready for testing.

That is the setup complete. Your editor now points to the folder that will hold the entire project.

Your workspace is ready. Next, you will create index.html and build the first version of the grid.

Build the Responsive Grid

The later coordinate math needs a real layout to measure. CSS Grid places each item according to the available width.

In this step, you will create one HTML file called index.html with twelve numbered cells. You will preview the responsive layout in Google Chrome or Microsoft Edge.

In this step, get ready to:
  • Create a single HTML demo with twelve numbered items.
  • Style the items as a responsive CSS Grid.
  • Preview the grid reflow in Chrome or Edge.
Create the single-file structure

A single-file demo keeps the entire baseline in one place. Every later measurement stays easy to inspect.

  • Use the new-file control in the editor's file sidebar to create index.html inside the project folder.

You should see index.html appear in the editor's file sidebar.

What belongs in the baseline?

  • Document metadata identifies the page as an HTML demo.
  • An internal style area keeps the CSS in the same file.
  • The page body holds one grid container.
  • The container holds twelve child items numbered from 1 through 12.
  • Each child displays only its number at this stage.
  • Add the complete baseline structure described above to index.html.
  • Save index.html.
  • Press Cmd+Space on macOS or the Windows key on Windows to open your search bar.
  • Type Google Chrome or Microsoft Edge into the search bar. Press Enter to open your chosen browser.
  • Use the browser's file-opening control to select index.html from the project folder.

You should see the numbers 1 through 12 in document order. Your first visible slice is working in the browser.

Seeing a blank page?

  • Confirm index.html is saved in the project folder.
  • Confirm the browser opened index.html.

Help me find why my local HTML page is blank.

Add the responsive base styles

The grid needs to adapt when the browser width changes. Its base styles also make every numbered item easy to distinguish.

What should the base styles cover?

  • The page gives the grid enough room to respond to the browser width.
  • The grid creates equal-width columns.
  • The column count adapts to the available space.
  • Consistent spacing separates the cells.
  • Each numbered cell appears as a distinct visual block.
  • Switch back to the index.html tab in the editor from earlier.
  • Add the complete base style layer described above to the internal style area.
  • Save index.html.
  • Return to the browser from earlier.
  • Refresh the page.

The numbers should now sit inside distinct visual cells. The grid should use the browser width to decide how many columns fit.

Still seeing plain text?

  • Confirm the grid layout rule is applied to the parent container.
  • Confirm each numbered child receives the cell styles.
  • Confirm the browser refreshed after you saved index.html.

Help me troubleshoot why my responsive grid styles are not appearing.

Verify the responsive reflow

Before you resize the window, how do you expect the number of columns to change as the available width shrinks?

  • Resize the browser window to a narrower width.

You should see the column count drop as space tightens. All twelve items stay visible in the same order.

  • Resize the browser window to a wider width.

You should see more columns fit across the page. Each cell still shows only its number.

Strong start. Your twelve-item grid now reflows as the browser changes width.

Next, you will turn each cell into a measurement target. You will inspect its raw value in the browser.

Measure Each Item with a View Timeline

Your twelve-item CSS Grid already adapts its layout to the available width. However, HTML order cannot reveal the horizontal position where each item lands.

A view timeline turns that position into animation progress. This scroll-driven animation writes the measurement into --progress for inspection in Chrome DevTools or Edge DevTools.

In this step, get ready to:
  • Register --progress as an animatable number.
  • Give every .item its own horizontal view timeline.
  • Compare the raw measurements across different grid columns.
Add the measurement animation

A custom property needs a numeric type before the browser can interpolate it smoothly. The grid also needs to act as the position reference for each item.

  • In index.html, locate the closing </style> tag.
  • Add this CSS immediately above the closing tag:
@property --progress {
  /* Store the measured position as an animatable number */
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.grid {
  /* Make the grid the position reference for its items */
  overflow: hidden;
}

.item {
  /* Track horizontal position and expose it as a raw value */
  view-timeline: --x x;
  timeline-scope: --x;
  animation: measure-position linear both;
  animation-timeline: --x;
}

/* Map timeline progress onto the custom property */
@keyframes measure-position {
  from {
    --progress: 1;
  }

  to {
    --progress: 0;
  }
}

What does this CSS measure?

  • The @property rule registers --progress as a number that can move between decimal values.
  • The overflow declaration makes .grid the position reference for its items.
  • The view-timeline declaration tracks each item's position on the horizontal x axis.
  • The timeline-scope declaration keeps every item connected to its own timeline named --x.
  • The keyframes map timeline progress from 1 to 0 through the --progress property.
  • Save index.html.
  • Switch back to the browser preview from earlier.
  • Refresh the page.
  • Right-click the first grid item.
  • Choose the option that opens the selected element in DevTools.
  • Use the computed-property search field to find --progress.

You should see --progress with a decimal value between 0 and 1. This confirms that the item's position is driving the keyframes.

Compare raw values across the grid

Before you inspect another cell, do you expect an item in a different column to report the same value or a different value?

  • Select a grid item from another column in the DevTools element tree.
  • Check its computed --progress value.
  • Repeat the check with one more item in a different column.

The items in different columns should report different raw values. That is the measurement layer working.

Each item now carries a numeric signal tied to its horizontal position. The browser recalculates that signal when the layout changes.

Do all items show the same value?

Confirm that the .grid element wraps every .item. Check that its overflow declaration is active.

Make sure animation-timeline appears after the animation shorthand. The shorthand resets the timeline when it comes later.

Use Chrome or Edge for this check. Other browsers may ignore part of the measurement system.

Help me debug identical --progress values across my CSS Grid items.

Your grid can now measure horizontal position without JavaScript. Next up, you will convert each raw value into a readable column index.

Calculate and Display Columns

Your view timeline now gives every CSS Grid item a position-dependent --progress value. That proves each item can measure where it sits horizontally.

Raw decimal values are difficult to compare with grid lines. This step converts each measurement into a one-based column index.

In this step, get ready to:
  • Measure the grid width with container query units.
  • Convert --progress into a calculated --column index.
  • Render the calculated column number inside every grid item.
Calculate the column index

A container query unit measures part of the nearest query container. Setting the grid as that container makes 100cqw equal to its full inline width.

  • In index.html, find the existing .grid CSS rule.
  • Add the container-type declaration from the code below inside .grid.
  • Find the existing .item CSS rule.
  • Add the two custom property declarations from the code below inside .item.
.grid {
  /* Let each item measure the grid with container query units. */
  container-type: inline-size;
}

.item {
  /* Count the tracks and convert progress into a one-based column. */
  --n: round(down, calc((100cqw + var(--g)) / (var(--c) + var(--g))));
  --column: round(calc(var(--progress) * (var(--n) - 1) + 1));
}

What Does This Calculation Do?

  • The 100cqw value represents the full inline width of .grid.
  • The --n property counts how many complete column tracks fit across that width.
  • The --column property scales --progress across the available column range.
  • The final + 1 makes the first column use index 1.
  • Save index.html.
  • Switch back to Chrome or Edge.
  • Inspect any .item in DevTools.
  • Check the computed custom properties for that item.

You should see an integer in --n. You should also see a one-based value in --column.

Seeing an Invalid Column Value?

  • Check that container-type: inline-size sits inside the existing .grid rule.
  • Confirm that the existing --c value matches the grid column size.
  • Confirm that the existing --g value matches the grid gap.

Help me debug the calculated column value in my CSS grid.

Display the column number

A custom property cannot appear as visible text by itself. A CSS counter turns the integer stored in --column into generated content.

  • In index.html, find the end of the existing .item CSS rule.
  • Add the following pseudo-element rule below it:
.item::after {
  /* Render the calculated integer as a visible column label. */
  counter-reset: column var(--column);
  content: "Column " counter(column);
}

How Does the Label Appear?

  • The counter-reset declaration loads the calculated --column value into a counter named column.
  • The content declaration prints that counter after the existing item content.
  • The label updates from the same position measurement that produced --progress.
  • Save index.html.
  • Before you refresh, predict whether the first item in each row returns to column 1.
  • Return to the grid in Chrome or Edge.
  • Refresh the page.

You should see the labels count from Column 1 across every row. Each new row should restart at Column 1.

Column Labels Missing or Repeated?

  • Check that the selector is written as .item::after.
  • Confirm that counter-reset uses var(--column).
  • Inspect an affected item to confirm that --progress still has a measured value.

Help me troubleshoot missing or repeated column labels in my grid.

Your grid items can now report their horizontal positions. Next, you will apply the same measurement pattern vertically so every item can report its row.

Calculate and Display Rows

Your grid already turns each item's horizontal position into --column. The next coordinate is its row.

A horizontal measurement cannot distinguish items stacked in the same column. This step adds a vertical view timeline that feeds --row through the same CSS math pattern.

In this step, get ready to:
  • Create an independent vertical position measurement.
  • Convert the vertical measurement into a row index.
  • Display both coordinates inside every grid item.
Measure each item's vertical position

The current --progress measurement belongs to the horizontal timeline. Its vertical twin needs a separate timeline.

The new animation stores its raw vertical measurement in --row-progress.

  • Return to index.html in the editor from earlier.
  • Locate the complete view timeline measurement that animates --progress for each .item.
  • Add a vertical copy of the complete --progress measurement as --row-progress.
  • Save index.html.
  • Switch back to the browser preview from earlier.
  • Refresh the page.
  • Inspect --row-progress on an item near the top in DevTools.
  • Inspect --row-progress on an item in a lower row.

The lower item should have a different raw vertical value. That is the second position measurement working.

Why use a separate measurement?

Each timeline measures one axis. Keeping --progress separate from --row-progress lets the grid calculate both coordinates independently.

Vertical value stays fixed?

Check that the copied measurement uses the vertical axis. Confirm its animation writes to --row-progress.

Leave --progress assigned to the horizontal measurement.

Help me debug a vertical CSS view timeline whose custom property does not change between grid rows.

Calculate the row index

The raw vertical value needs the same conversion that produced --column. The new formula uses the grid's height as its measuring scale.

How does the row formula work?

  • Use --row-progress as the raw vertical position.
  • Use vertical container query units to measure the grid's height.
  • Use calc() to scale the measurement by the row track size.
  • Use round() to produce a whole row index.
  • Return to the .item rule in index.html.
  • Locate the formula that calculates --column.
  • Build --row from the copied --column pattern using the vertical inputs above.
  • Save index.html.
  • Refresh the browser preview.
  • Inspect --row on an item in the top row.
  • Inspect --row on an item in a lower row.

Items in the same horizontal band should share a row index. Each lower band should move to the next index.

The row calculation is now live. Every item has a vertical coordinate that matches its grid position.

Row indexes look wrong?

  • Confirm that --row reads from --row-progress.
  • Confirm that the formula uses the vertical container query unit.
  • Confirm that the formula uses the grid's row track size.

Help me correct the row index formula in my responsive CSS grid.

Display both coordinates

The row value becomes useful when you can compare it with the visible layout. Each item can reuse the generated text that already prints --column.

  • Extend the existing generated text in index.html so each .item prints --row beside --column.
  • Save index.html.

Before you refresh, predict which items will share a row number.

  • Refresh the browser preview to test your prediction.

You should see both coordinate values inside all twelve items. Items aligned horizontally should share a row value.

That's the second axis solved. Every item can now report its position in two dimensions.

Only see the column value?

Confirm that the generated text reads --row from the same .item rule that calculates it.

Check the row counter name for typos if the label appears without a number.

Help me display both calculated coordinates inside every CSS grid item.

Both coordinates are visible. Next, you'll challenge the formulas with items that span extra grid tracks.

Handle Spanning Grid Items

Your grid now prints a row index and a column index for every single-cell item. Spanning items reveal whether those numbers still describe the true starting edge.

A view timeline measures the position of an item's whole box. A box that grows across extra tracks exposes the single-cell assumption inside the current coordinate math.

In this step, get ready to:
  • Add horizontal and vertical spans to two grid items.
  • Observe how larger item dimensions affect the printed coordinates.
  • Restore the correct starting coordinates for every item.
Expose the span problem

A span lets one item occupy multiple tracks in CSS Grid. The existing formulas have only measured one-track boxes so far.

  • Switch back to index.html from the previous step.
  • Style one designated .item with a grid-column span across two tracks.
  • Save index.html.
  • Predict whether the wider item keeps the correct starting column label.
  • Switch back to the Chrome tab from earlier.
  • Refresh the page.

You will see one cell stretch across two columns. Its printed column can drift away from the column where its left edge begins.

  • Style a different .item with a grid-row span across two tracks.
  • Save index.html.
  • Refresh the browser preview.

You will see another cell stretch across two rows. Its printed row can now miss the row where its top edge begins.

Why do the coordinates drift?

The timeline progress depends on the space through which an item can travel. A wider item has less horizontal travel available inside the same grid.

A taller item has less vertical travel available. The old scaling assumes every item has the same dimensions.

Correct the starting-edge math

A reliable starting coordinate uses the travel available to the current item. Each axis therefore needs the container dimension minus the item's dimension before the result is converted into track steps.

  • In index.html, locate the existing --column declaration.
  • Rewrite its calc() scaling term to use the available horizontal travel over one complete column step.
  • Calculate the available horizontal travel from the container width minus the current .item width.
  • Keep the existing round() operation around the final column value.
  • Save index.html.
  • Predict whether the wider item now reports the column where its left edge begins.
  • Refresh the browser preview.

The wider item now reports its starting column. The single-width items keep their existing column values.

  • In index.html, locate the existing --row declaration.
  • Rewrite its calc() scaling term to use the available vertical travel over one complete row step.
  • Calculate the available vertical travel from the container height minus the current .item height.
  • Keep the existing round() operation around the final row value.
  • Save index.html.
  • Predict whether the taller item now reports the row where its top edge begins.
  • Refresh the browser preview.

The taller item now reports its starting row. Every one-track item keeps the same coordinates it displayed before.

Still seeing a shifted coordinate?

Check that the column calculation uses the item's width. Check that the row calculation uses the item's height.

Confirm that round() still wraps each normalized result. Removing it can leave a fractional value between two tracks.

Help me debug coordinate formulas for spanning CSS Grid items.

Verify both spanning items

A spanning item's coordinate belongs to its top-left occupied cell. The extra tracks change its size without changing that starting point.

  • Count from the grid's left edge to the first column occupied by the wider item.
  • Count from the grid's top edge to the first row occupied by the taller item.
  • Predict whether both printed labels match those starting positions.
  • Refresh the browser preview.
  • Compare the wider item's printed column with its leftmost occupied column.
  • Compare the taller item's printed row with its topmost occupied row.

That edge case is handled. Every item now reports its starting row and column even when its box occupies extra tracks.

Your coordinate system now survives different item sizes. Next up, you will feed those row and column indexes into a diagonal animation delay.

Stagger the Grid Diagonally

You have a responsive CSS Grid where every item reports its starting coordinates. The two spanning items keep those coordinates accurate after a reflow.

Without a timing link, every item starts together even though its coordinates differ. A row-plus-column sum turns those coordinates into diagonal timing waves.

In this step, get ready to:
  • Calculate a diagonal delay from each item's coordinates.
  • Keep the animation working in Firefox or Safari with a plain fallback.
  • Replay the stagger in Chrome or Edge after a reflow.
Turn coordinates into a delay

Cells on the same diagonal share the same row-plus-column total. That total gives their transitions a shared start time.

  • Switch back to index.html from earlier.
  • Find the .item rule that contains the coordinate calculations.
  • Set transition-delay to a calc() expression that sums --row with --column before scaling the result by the existing timing interval.
  • Save index.html.

Before you refresh, which items do you expect to begin their transitions at the same time?

  • Refresh the demo in Chrome or Edge.

You'll see items with smaller coordinate sums begin first. Items with equal sums form each diagonal wave.

Does every item start together?

Confirm that transition-delay references both --row plus --column inside the .item rule.

Check that the calculated delay uses the same time unit as the existing item transition.

Help me debug the diagonal transition delay.

Protect the animation with a fallback

A plain delay keeps the item transition valid when a browser cannot evaluate the coordinate calculation. Declaration order lets browsers use the most capable value they understand.

  • Place a plain transition-delay in the .item rule before the coordinate-driven declaration.
  • Set the plain delay to the timing interval used by the coordinate-driven calculation.
  • Add a CSS comment above the coordinate-driven declaration that notes only Chrome and Edge support the full set of features today.
  • Save index.html.
  • Confirm that the plain declaration appears before the coordinate-driven declaration.

Chrome and Edge use the later coordinate calculation. Firefox and Safari retain the earlier plain delay.

Why does the fallback come first?

CSS uses the later declaration when the browser understands its value. An unsupported value leaves the earlier valid delay active.

This ordering preserves the entrance transition across browsers while enabling diagonal timing where the full feature set is available.

Test the stagger after reflow

The final check proves that the delay comes from each item's live coordinates. Changing the column count gives the grid a new set of diagonal groups.

Before you resize the window, which diagonal do you expect each item to join after its coordinates change?

  • Return to the browser tab showing your demo.
  • Narrow the browser window until the grid uses fewer columns.
  • Reload the page to replay the item animation.

You'll see all twelve items settle into the narrower grid. Their printed row values update as the items wrap.

The spanning items still show their correct starting coordinates. The items enter in diagonal waves based on the reflowed positions.

  • Widen the browser window until the grid adds columns.
  • Reload the page to replay the animation again.

You'll see the coordinates recalculate for the wider layout. The diagonal order follows those updated values.

You have closed the loop. Your stagger now follows the live grid after every reflow.

Secret mission

Stress-Test the Responsive Grid

A responsive effect needs evidence that it survives more than one comfortable layout. Build a three-state regression test for coordinates, spanning items, and stagger order. Finish by proving the plain fallback keeps the grid usable in Firefox or Safari.

Clean Up Your Resources

Clean Up Your Resources

Your finished demo lives in the local index.html file. Keeping it has no ongoing cost.

Resources you used:

  • The local index.html file containing your complete responsive grid animation demo.

Keep everything running

No action is needed. Choose this option if you want to keep testing grid reflows or refining the animation.

  • Keep index.html in its current location.
  • Return to the open browser tab whenever you want to replay the responsive animation.

Pause - I'll come back to this later

Pausing this project closes the displayed demo. The local file stays ready for your return.

  • Close the browser tab that displays index.html.
  • Leave index.html in its current location.

The demo is no longer open in your browser. Your code remains available whenever you want to continue.

Delete - I don't want to use this again

Deleting a finished project can feel drastic. The index.html file is the only project resource this option removes.

  • Locate index.html in the folder where you saved it.
  • Move index.html to your Trash on macOS or Recycle Bin on Windows.
  • Empty your Trash on macOS or Recycle Bin on Windows to remove the file permanently.
  • Refresh the browser tab that displayed the demo.

You should see that the local page is unavailable. That confirms the demo file is gone.

Nice Work!

Nice Work!

You did it! Your single-file responsive CSS Grid demo now calculates each item's row and column after every reflow. Those coordinates drive a diagonal stagger that follows the layout.

You've learned how to:

  • Measure each item's reflowed position with view timelines, custom properties, and container query units.
  • Convert raw measurements into accurate row and column indexes with CSS math functions. Spanning items still report their correct starting coordinates.
  • Create a diagonal animation stagger from the calculated coordinates. Resize the responsive grid to watch the sequence adapt to its new layout.
  • Secret Mission: Complete an optional challenge to extend your coordinate-aware grid animation.

Ready to quiz yourself?