Scroll Crossword Fill Reveal — Free HTML CSS JS Snippet

Scroll Crossword Fill Reveal · Scroll · Plain HTML, CSS & JS · Live preview

What's included

Features

Plain CSS grid layout — no canvas, no absolute-positioned letter tiles
One scrubbed GSAP timeline drives every cell's pop-in and highlight flash together
back.out(2) ease gives each letter a satisfying slight-overshoot "snap into place"
Blocked/blank squares are a simple CSS class, filtered out of the animated cell list
Brief backgroundColor flash on each cell simulates a pen just having filled it in
Fully reversible — scrolling up empties the grid in reverse stagger order
Classic crossword palette: navy grid lines, white cells, pale blue fill-in flash
Short, precise ScrollTrigger start/end window keeps the reveal tied to the grid entering view

About this UI Snippet

Scroll Crossword Fill Reveal — Staggered Grid Reveal with a Scrubbed Timeline

Screenshot of the Scroll Crossword Fill Reveal snippet rendered live

A puzzle-style scroll reveal where a CSS grid of crossword cells fills in one at a time as the section scrolls into view, each cell popping into place and briefly flashing a highlight color like a pen just wrote it in. Pair with Scroll Text Clip Reveal for a complementary letter-driven effect, or Scroll Reveal Grid patterns for a simpler staggered reveal.

A CSS grid, not a canvas

The puzzle is a plain display: grid of .cell divs with grid-template-columns: repeat(6, 44px). Filled black squares between words are separate .cell.blank divs that stay opaque from the start — no JavaScript needed to distinguish playable cells from blocked ones, it's a CSS class.

One scrubbed timeline, one tween per cell

gsap.utils.toArray('.cell:not(.blank)') collects every playable cell in document order. A single timeline loops over them, giving cell i a start position of i * 0.06 on the shared timeline. Each cell gets a fromTo popping it from scale(0.5)/opacity:0 to full size with a back.out(2) ease for a slight overshoot, like a letter snapping into its box.

The "just filled in" flash

Right after each cell's pop-in tween, two more tweens at the same and nearby timeline positions animate backgroundColor from white to a pale blue and back to white — a quick highlight flash that reads as "this cell was just written," independent of the scale/opacity pop.

Why start/end target only the grid's entrance

The ScrollTrigger's start: 'top 75%' / end: 'top 5%' window is deliberately short — just the distance it takes the grid to scroll from mostly-below-viewport to near the top. Because it's all one scrubbed timeline, scrolling back down below the start point resets every cell back to invisible/unscaled, and scrolling past the end point leaves the completed grid fully filled.

Reversibility for free

Because every letter's appearance is driven by scroll-mapped timeline position rather than a one-shot trigger, scrolling up "erases" the puzzle in the same staggered order it filled in, which is what makes this feel fundamentally different from a fire-once IntersectionObserver reveal.

Build with AI

Build, Understand, Optimize, and Extend It With AI

This snippet's core idea — mapping array index to timeline position (i * constant) inside one scrubbed GSAP timeline — is a reusable pattern for any staggered, scroll-reversible reveal, not just crosswords. Ask an AI assistant to explain why this produces a *reversible* stagger where a CSS animation-delay-based stagger would not, and to walk through what would need to change to make the stagger diagonal (by grid row+column) instead of purely index-based. It's also useful for extending the effect: ask for a version where each cell's letter is typed in character-by-character, or one that adds a celebratory pulse across the whole grid once the last cell fills in. Use it as a working reference to adapt, not a finished puzzle game.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a scroll-scrubbed "crossword fill-in" animation in plain HTML, CSS, and JavaScript using GSAP and ScrollTrigger — a real CSS grid, no canvas.

Requirements:
- Create a CSS grid of square cells representing a small crossword (display: grid, fixed cell size, small gap, dark grid-line background showing through the gaps).
- Give some cells a "blank/blocked" class with a solid dark background and full opacity from the start — these should NOT be part of the animated reveal.
- Give the remaining "letter" cells a single centered letter character each, and start them fully transparent and scaled down (opacity: 0, transform: scale(0.5)).
- In JavaScript, collect only the non-blank letter cells (e.g. via a ":not(.blank)" selector) in their document order.
- Create one GSAP timeline attached to a ScrollTrigger with a numeric scrub value and a start/end range tied to the puzzle section scrolling from mostly below the viewport up to near the top (e.g. start: "top 75%", end: "top 5%") — do not pin the section.
- Loop over the collected cells and, for each one at index i, add a fromTo tween to the shared timeline at position (i multiplied by a small constant like 0.06) that animates opacity from 0 to 1 and scale from 0.5 to 1, using a back-out easing curve for a slight overshoot pop.
- At the same or a slightly later timeline position for each cell, add one or two more short tweens that animate the cell's background-color from white to a pale highlight color and back to white, simulating a "just filled in" flash independent of the pop-in motion.
- Confirm that scrolling back up reverses the whole sequence — cells shrink back to invisible in reverse order — since it's all driven by one scrubbed timeline's playhead position.
- Style it with a classic crossword palette: white cells, navy or black grid lines, and a pale blue highlight flash color.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="hint">Scroll ↓ to fill in the puzzle</div>
<section class="puzzle-section">
  <div class="grid" id="grid">
    <div class="cell" data-n="1"><span class="num">1</span><span class="letter">S</span></div>
    <div class="cell" data-n="2"><span class="letter">C</span></div>
    <div class="cell" data-n="3"><span class="letter">R</span></div>
    <div class="cell" data-n="4"><span class="letter">O</span></div>
    <div class="cell" data-n="5"><span class="letter">L</span></div>
    <div class="cell" data-n="6"><span class="letter">L</span></div>
    <div class="cell blank"></div>
    <div class="cell" data-n="7"><span class="num">2</span><span class="letter">G</span></div>
    <div class="cell" data-n="8"><span class="letter">S</span></div>
    <div class="cell" data-n="9"><span class="letter">A</span></div>
    <div class="cell" data-n="10"><span class="letter">P</span></div>
    <div class="cell blank"></div>
    <div class="cell" data-n="11"><span class="num">3</span><span class="letter">T</span></div>
    <div class="cell" data-n="12"><span class="letter">R</span></div>
    <div class="cell" data-n="13"><span class="letter">I</span></div>
    <div class="cell" data-n="14"><span class="letter">G</span></div>
    <div class="cell" data-n="15"><span class="letter">G</span></div>
    <div class="cell" data-n="16"><span class="letter">E</span></div>
    <div class="cell" data-n="17"><span class="letter">R</span></div>
  </div>
  <div class="clues">
    <p><strong>1 across</strong> — moves a page vertically</p>
    <p><strong>2 across</strong> — the space between two points</p>
    <p><strong>3 across</strong> — what starts an animation</p>
  </div>
</section>

Step by step

How to Use

  1. 1
    Scroll the grid into viewScroll down until the crossword grid enters the upper part of the viewport — cells pop in one by one with a brief blue highlight flash.
  2. 2
    Scroll back upThe grid empties out in the same staggered order, confirming the effect is fully reversible.
  3. 3
    Change the puzzle contentEdit the letters inside each .letter span and the data-n clue numbers in the HTML panel to spell a different word or phrase.
  4. 4
    Adjust the grid shapeChange grid-template-columns (currently repeat(6, 44px)) in the CSS panel to make a wider, narrower, or differently-sized grid.
  5. 5
    Tune the stagger speedChange the 0.06 multiplier in pos = i * 0.06 in the JS panel to make cells fill in faster or slower relative to each other.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for React, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Puzzle or word-game landing page
Showcase a crossword, word-game, or trivia app with a playful scroll-triggered demonstration of the grid filling in.
Educational vocabulary or spelling reveal
Repurpose the grid to spell out a key term or concept as students scroll through a lesson page.
Brand name or tagline reveal
Use the crossword-fill motion as a distinctive way to spell out a brand name, product name, or short tagline on a hero section.
Learn staggered grid animation with a scrub timeline
Study how per-item start positions (i * constant) inside one scrubbed timeline produce a stagger that stays perfectly reversible.
Print or publishing brand scroll section
A natural fit for newspaper, magazine, or puzzle-book brands wanting a scroll moment that nods to their print heritage.

Got questions?

Frequently Asked Questions

Blank squares use an extra .blank CSS class that sets opacity: 1 and a dark background from the start. The JS collects animated cells with gsap.utils.toArray(".cell:not(.blank)"), so blanks are simply skipped by the query selector.

They are two separate tweens placed at the same (or a slightly offset) timeline position: one animates scale/opacity for the "appearing" motion, another animates backgroundColor for the "just written in" highlight. Layering simple tweens like this is easier to tune than one complex keyframe animation.

Every cell's appearance is a tween inside one scrubbed timeline mapped to a specific scroll range. Scrubbing the timeline backward runs those tweens in reverse, so cells shrink back to invisible in the same order they appeared.

Yes — add more .cell divs to the grid and increase grid-template-columns' repeat count in the CSS. The JS automatically staggers however many non-blank cells exist, no changes needed there.

The reveal is meant to complete quickly as the grid scrolls into the upper viewport, similar to a staggered card-grid reveal — pinning isn't needed because the grid doesn't need to hold the viewport's attention beyond its own entrance.