Skip to content
Sign in
EffectsBlur up imageMenu

Blur up imagePro

An image that paints a ThumbHash placeholder in the first frame, in color, grayscale or a 1 bit dither, and crossfades to the photo once it is decoded.

ProThree photos with color, grayscale and dithered ThumbHash placeholders. Replay holds the URLs back for 1.2 s.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/blur-up-image

Usage

tsx
import { BlurUpImage } from "@/components/ui/chatcn/pro/blur-up-image"

<BlurUpImage src={image.url} alt={image.alt} thumbhash={image.thumbhash} tone="grayscale" />

Store a ThumbHash with each image at upload time: draw it at 100 px or less and call rgbaToThumbHash from @/lib/chatcn-pro-placeholders-thumbhash (a TypeScript port of ThumbHash by Evan Wallace, MIT). It is about 28 bytes. The component decodes it to a PNG data URL in plain JS, so it renders on the server and paints before any script runs, with the right colors and aspect ratio.

No ThumbHash? Pass any small data URL as placeholder (BlurHash output, a 16 px JPEG). grayscale keeps a monochrome page monochrome until the photo lands; dithered renders the placeholder as an ordered dither in ink and paper.

Examples

States

Waiting for the URL, an expired link with retry, a coarse dither, a loaded photo, a square crop of a 4:3 photo and right to left.

ProWaiting for the URL, an expired link with retry, a coarse dither, a loaded photo and a square crop.

Props

PropTypeNotes
srcstringThe photo. Leave it out while the URL is not ready yet
altstringRequired
thumbhashUint8Array | stringThumbHash bytes or base64
placeholderstringAny small data URL, used when there is no thumbhash
tone"color" | "grayscale" | "dithered"Default color
algorithm, pixelSizeDither pattern and cell size. Default bayer-4, 2 px
ink, paperstringDither tones. Default fg-subtle and inset tokens
aspectRationumber | "3:2"Default width / height, then the ratio in the ThumbHash
width, height, sizes, srcSet, loading, fetchPriorityPassed to <img>
errorText, retryLabelstringDefault "Image unavailable" and "Retry"
onStatusChange(status: "loading" | "loaded" | "error") => void
imgClassNamestringClasses for the photo

Keyboard

KeyAction
TabReaches Retry on the error chip. The image takes no focus

Accessibility

  • The placeholder is aria-hidden with an empty alt. The photo carries your alt; while there is no src yet, an empty role=img with the same name stands in.
  • The error chip is role=alert and its Retry button has a 32 px hit area (44 px on touch) around the small visual.
  • Under forced-colors: active the photo shows at full opacity, with no fade.

Motion

  • Only opacity moves: a 300 ms crossfade on the soft out curve, started after img.decode() resolves. The blur radius never animates.
  • A photo already in the cache shows at once, with no fade and no placeholder flash.
  • Once the fade ends the placeholder is removed from the DOM.
  • Under reduced motion the crossfade is 150 ms.