Skip to content

Pixel Image

An image that resolves from coarse pixel blocks to sharp when it scrolls into view. The real image is always in the page (server HTML, no JavaScript, print); the blocks are a hidden canvas drawn over it and removed when done.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

Green highlands under a cloudy sunrise with a winding road
import { PixelImage } from "@rdloom/react";

// A free-to-use photo from Unsplash. Swap in your own image.
const picture = "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=1200&q=80";

// The sharp image is in the page from the start. The blocks are drawn over it and fade away in about a second.
export default function PixelImageBasicExample() {
  return (
    <div className="flex w-full justify-center">
      <PixelImage src={picture} alt="Green highlands under a cloudy sunrise with a winding road" className="w-full max-w-xl rounded-[var(--rd-radius-overlay)]" />
    </div>
  );
}

Installation

npx rdloom add pixel-image

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/pixel-image.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { PixelImage } from "@rdloom/react";

<div className="flex w-full justify-center">
  <PixelImage src={picture} alt="Green highlands under a cloudy sunrise with a winding road" className="w-full max-w-xl rounded-[var(--rd-radius-overlay)]" />
</div>

Children

A misty valley with pine trees and a river at sunrise
import { PixelImage } from "@rdloom/react";

// A free-to-use photo from Unsplash. Swap in your own image.
const picture = "https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80";

// Pass your own image element when you need a width, a height, srcSet or object-fit.
export default function PixelImageChildrenExample() {
  return (
    <div className="flex w-full justify-center">
      <PixelImage className="w-full max-w-xl rounded-[var(--rd-radius-overlay)]">
        <img src={picture} alt="A misty valley with pine trees and a river at sunrise" width={1200} height={800} className="block h-64 w-full object-cover" />
      </PixelImage>
    </div>
  );
}

Block size

A misty valley with pine trees and a river at sunrise
import { PixelImage } from "@rdloom/react";

// A free-to-use photo from Unsplash. Swap in your own image.
const picture = "https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80";

// Bigger first blocks and a longer resolve make the effect easier to see.
export default function PixelImageBlockSizeExample() {
  return (
    <div className="flex w-full justify-center">
      <PixelImage src={picture} alt="A misty valley with pine trees and a river at sunrise" blockSize={64} duration={2} className="w-full max-w-xl rounded-[var(--rd-radius-overlay)]" />
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
children

Your own image element. Use this or src plus alt.

nodenone
src

Image URL, when not passing children.

stringnone
alt

Alternative text for src. Use an empty string for a decorative image.

stringnone
blockSize

Size in px of the first, coarsest blocks. The blocks halve each step until the image is sharp.

number32
duration

Seconds for the whole resolve.

number1.2
whenInView

Wait until the image scrolls into view before resolving.

booleantrue
isStill

Shows the sharp image at once.

booleanfalse

Accessibility

Role img, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • Read as an ordinary image with its alt text; the effect is not announced

What your code must do

  • The real image is always rendered first, with its alt text, so it is complete with no JavaScript and in print
  • The canvas of blocks is decoration: aria-hidden, pointer-events none, removed when the effect finishes
  • The effect runs once and lasts about a second; it never loops and never flashes
  • Skipped for prefers-reduced-motion, inside rdm-still and with isStill: the sharp image is shown
  • If the canvas cannot be used the image simply stays visible
  • Forced-colors: the canvas is hidden

Guidelines

Use it when

  • A hero or feature picture that should arrive with a little character

Avoid it when

  • Images the reader must see immediately
  • Many images in one view

Don't

  • Omitting alt text
  • Using it on every image on a page