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.
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.
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-imageCopies 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.jsonWorks, 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
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
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.
| Prop | Type | Default |
|---|---|---|
childrenYour own image element. Use this or src plus alt. | node | none |
srcImage URL, when not passing children. | string | none |
altAlternative text for src. Use an empty string for a decorative image. | string | none |
blockSizeSize in px of the first, coarsest blocks. The blocks halve each step until the image is sharp. | number | 32 |
durationSeconds for the whole resolve. | number | 1.2 |
whenInViewWait until the image scrolls into view before resolving. | boolean | true |
isStillShows the sharp image at once. | boolean | false |
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