Aaditya

Dither Image

Component

About

Any photo as 1 bit dither art. Atkinson, Floyd-Steinberg, Bayer and blue noise, run in a WebGL shader or a worker, with cell size, ink and paper. An optional reveal shows the original on hover.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Dither Image to your project and fits it to your theme.

Code

How Dither Image is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client"

import * as React from "react"
import { cn } from "@/lib/utils"
import { Toggle } from "@base-ui/react/toggle"
import { ToggleGroup } from "@base-ui/react/toggle-group"
import { DitherAlgorithm, DitherImage } from "@/components/ui/dither-image"

const reducedMotion = () =>
  typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches

/**
 * Steps through `values` on a timer, so a 21st preview (and its cover video) shows every option
 * without a click. Picking a value stops the tour. Reduced motion never starts it.
 */
function useCycle<T>(values: readonly T[], ms: number) {
  const [index, setIndex] = React.useState(0)
  const [held, setHeld] = React.useState(false)
  React.useEffect(() => {
    if (held || reducedMotion()) return
    const id = window.setInterval(() => setIndex((n) => (n + 1) % values.length), ms)
    return () => window.clearInterval(id)
  }, [held, values.length, ms])
  const pick = React.useCallback(
    (value: T) => {
      setHeld(true)
      setIndex(Math.max(0, values.indexOf(value)))
    },
    [values],
  )
  return [values[index] as T, pick] as const
}

const ALGORITHMS = [
  { value: "atkinson", label: "Atkinson" },
  { value: "floyd-steinberg", label: "Floyd-Steinberg" },
  { value: "bayer-8", label: "Bayer 8" },
  { value: "blue-noise", label: "Blue noise" },
] as const satisfies readonly { value: DitherAlgorithm; label: string }[]

const ALGORITHM_VALUES = ALGORITHMS.map((a) => a.value)

const FIT_CSS = "html,body{overflow:hidden}[data-fit-small] [data-fit-hide]{display:none!important}"

/**
 * 21st shows a demo in a small frame: about 674 by 421 on a component page, 358 by 224 on a
 * phone and 245 by 139 on a profile card. A demo taller than its frame gets scrollbars, which
 * render white on dark pages. FitFrame fills the frame exactly instead.
 *
 * - "scale": the content shrinks until it fits, centered. For visuals such as orbs.
 * - "crop": the content stays at full size from the top and fades out at the bottom. For text
 *   that streams in, where a changing scale would make the page breathe.
 *
 * Anything marked `data-fit-hide` (state buttons, the chatcn.dev line) drops out in frames
 * shorter than `hideBelow`, so a profile card shows the visual alone.
 */
function FitFrame({
  children,
  mode = "scale",
  hideBelow = 360,
}: {
  children: React.ReactNode
  mode?: "scale" | "crop"
  hideBelow?: number
}) {
  const content = React.useRef<HTMLDivElement>(null)
  const [cropped, setCropped] = React.useState(false)

  React.useLayoutEffect(() => {
    const el = content.current
    if (!el) return
    const measure = () => {
      const vw = window.innerWidth
      const vh = window.innerHeight
      el.toggleAttribute("data-fit-small", vh < hideBelow)
      // Layout sizes ignore the transform, so measuring never feeds back.
      const w = Math.max(el.offsetWidth, el.scrollWidth)
      const h = Math.max(el.offsetHeight, el.scrollHeight)
      const scale = mode === "scale" ? Math.min(1, vw / w, vh / h) : 1
      el.style.transform = scale < 1 ? `scale(${scale})` : ""
      setCropped(mode === "crop" && h > vh + 1)
    }
    measure()
    const observer = new ResizeObserver(measure)
    observer.observe(el)
    window.addEventListener("resize", measure)
    return () => {
      observer.disconnect()
      window.removeEventListener("resize", measure)
    }
  }, [mode, hideBelow])

  const fade = "linear-gradient(to bottom, #000 calc(100% - 40px), transparent)"
  return (
    <div
      className="flex w-full justify-center overflow-hidden"
      style={{
        height: "100dvh",
        alignItems: mode === "crop" ? "flex-start" : "center",
        maskImage: cropped ? fade : undefined,
        WebkitMaskImage: cropped ? fade : undefined,
      }}
    >
      <style>{FIT_CSS}</style>
      <div
        ref={content}
        className="w-full shrink-0"
        style={{ transformOrigin: mode === "crop" ? "top center" : "center" }}
      >
        {children}
      </div>
    </div>
  )
}

// picsum.photos id 1027, 640 x 800, inline so the 21st preview needs no image host.
const portrait = {
  alt: "Portrait of a woman with dark curly hair and red lipstick, looking past the camera in soft light",
  width: 640,
  height: 800,
  src: "data:image/webp;base64,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",
}

/** A labeled segmented control on Base UI ToggleGroup. One value is always selected. */
function Segmented<T extends string>({
  label,
  value,
  onValueChange,
  options,
  className,
}: {
  label: string
  value: T
  onValueChange: (value: T) => void
  options: readonly { value: T; label: string }[]
  className?: string
}) {
  return (
    <div className={cn("flex min-w-0 flex-col gap-1.5", className)}>
      <span className="text-[0.75rem]/[1rem] text-(color:--chatcn-fg-subtle) font-medium">{label}</span>
      <ToggleGroup
        value={[value]}
        onValueChange={(next) => {
          const picked = next[0] as T | undefined
          if (picked) onValueChange(picked)
        }}
        aria-label={label}
        className="bg-(color:--chatcn-fill) rounded-(--chatcn-radius-md) flex h-8 w-fit max-w-full items-center gap-0.5 overflow-x-auto p-0.5 pointer-coarse:h-11"
      >
        {options.map((option) => (
          <Toggle
            key={option.value}
            value={option.value}
            className={cn(
              "chatcn-focus rounded-(--chatcn-radius-sm) text-[0.8125rem]/[1.25rem] inline-flex h-full shrink-0 items-center px-2.5 font-medium whitespace-nowrap",
              "text-(color:--chatcn-fg-subtle) ease-(--chatcn-ease-standard) transition-[color,background-color,box-shadow] duration-150",
              "hover:text-(color:--chatcn-fg) data-[pressed]:bg-(color:--chatcn-card) data-[pressed]:text-(color:--chatcn-fg) data-[pressed]:shadow-(--chatcn-shadow-xs)",
            )}
          >
            {option.label}
          </Toggle>
        ))}
      </ToggleGroup>
    </div>
  )
}

const SIZES = [
  { value: "1", label: "1 px" },
  { value: "2", label: "2 px" },
  { value: "3", label: "3 px" },
] as const

/** The line under each 21st demo that points back to the kit. */
function MoreOn({ slug, children }: { slug: string; children: React.ReactNode }) {
  return (
    <p data-fit-hide className="text-[0.75rem]/[1rem] text-(color:--chatcn-fg-faint) text-center text-balance">
      {children}{" "}
      <a
        href={`https://chatcn.dev/docs/components/${slug}`}
        target="_blank"
        rel="noopener"
        className="text-(color:--chatcn-fg-subtle) hover:text-(color:--chatcn-fg) underline underline-offset-2"
      >
        chatcn.dev
      </a>
    </p>
  )
}

/** A portrait through four dither methods in turn. */
export default function DitherImagePreview() {
  const [algorithm, setAlgorithm] = useCycle<DitherAlgorithm>(ALGORITHM_VALUES, 2600)
  const [pixel, setPixel] = React.useState<"1" | "2" | "3">("3")
  return (
    <FitFrame>
      <div className="flex w-full flex-col items-center justify-center gap-6 p-6">
        <div className="flex w-full max-w-2xl flex-col items-center gap-6 sm:flex-row sm:justify-center">
          <DitherImage
            src={portrait.src}
            alt={portrait.alt}
            algorithm={algorithm}
            pixelSize={Number(pixel)}
            aspectRatio="4:5"
            className="rounded-(--chatcn-radius-lg) w-full max-w-xs shrink-0"
          />
          <div data-fit-hide className="flex flex-col gap-4">
            <Segmented
              label="Method"
              value={algorithm}
              onValueChange={setAlgorithm}
              options={ALGORITHMS}
            />
            <Segmented label="Cell" value={pixel} onValueChange={setPixel} options={SIZES} />
          </div>
        </div>
        <MoreOn slug="dither-image">Seven methods, a hover reveal, shader or worker. On</MoreOn>
      </div>
    </FitFrame>
  )
}

Dependencies

More by Aaditya

View all
  • Dither Orb

    Dither Orb

    7
  • Pixel Orb

    Pixel Orb

    5
  • Wireframe Globe

    Wireframe Globe

    5
  • Audio Visualizer

    Audio Visualizer

    4
  • Dot Matrix Loader

    Dot Matrix Loader

    3
  • Halftone Loader

    Halftone Loader

    3
  • Contour Field

    Contour Field

    3
  • ASCII 3D

    ASCII 3D

    3

Explore more

Part of chatcn — browse the full library on 21st.dev.