Ryan Tseng

Ink Reveal

Cards 20

About

Image reveal card: a WebGL ink dissolve blooms from your cursor and develops the image like ink soaking into paper, wicking along the paper's fibers. Rest on a faint wash or on a second image. Hover, in view, scroll-scrubbed or controlled. React only, one WebGL context for any number of cards. npx @21st-dev/cli add @ryanonline1234/ink-reveal

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

Code

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

Usage.tsx

'use client'

// 21st's CLI points this import at its own copy of the component when it publishes.
import InkReveal, { type Origin, type Trigger } from '@/components/ui/ink-reveal'

const settings = {
  trigger: 'hover',
  origin: 'pointer',
  ease: 'ink',
  duration: 1200,
  progress: 0.5,
  rest: 0.2,
  feather: 0.14,
  grain: 0.3,
  noiseScale: 5,
  inkColor: '#1b1613',
  // each print rests on its partner instead of a faint wash: Tōhaku's two screens, Sesshū's two landscapes
  paired: false,
}

const PARTNER = [2, 3, 0, 1]

const WORKS = [
  {
    slug: 'pine-trees-right',
    title: 'Pine Trees, right screen',
    by: 'Hasegawa Tōhaku, late 16th c.',
    src: 'https://cdn.21st.dev/assets/mirror/5d/5d25798f2399889aed4ec167bb9992f09d8f8a6ef7a67646c0344f91d1a7aba5.jpg',
    alt: 'Pine trees emerging from mist, painted in ink on a folding screen',
  },
  {
    slug: 'splashed-ink-landscape',
    title: 'Splashed-ink landscape',
    by: 'Sesshū Tōyō, 1495',
    src: 'https://cdn.21st.dev/assets/mirror/21/212cd919b2d9976a978f3f3e75bf85a8872d4b08627136ac17f5bfb4bf531d53.jpg',
    alt: 'A mountain landscape in loose splashed ink, with a building and a figure in a boat',
  },
  {
    slug: 'pine-trees-left',
    title: 'Pine Trees, left screen',
    by: 'Hasegawa Tōhaku, late 16th c.',
    src: 'https://cdn.21st.dev/assets/mirror/f6/f6517f20aa55be6babbfd8c2d889553a270b5e8cb844e7071994b2e959ceab35.jpg',
    alt: 'Pine trees and a distant snowy peak in ink, from the left folding screen',
  },
  {
    slug: 'winter-landscape',
    title: 'Landscape of Four Seasons: Winter',
    by: 'attributed to Sesshū Tōyō, 15th c.',
    src: 'https://cdn.21st.dev/assets/mirror/26/26b2dbaa7415a67e33b7a84327f77e0cdc9a0a56bff7df3030533916276cade4.jpg',
    alt: 'A winter landscape in ink: cliffs, bare trees and a temple roof under snow',
  },
]

const HINT: Record<string, string> = {
  hover: 'Hover or tab to a print',
  inView: 'Scroll a print into view',
  scroll: 'Scroll the prints up the page',
  manual: 'Scrub the progress control',
}

export default function Demo(props: Partial<typeof settings> & { src?: string }) {
  const s = { ...settings, ...props }
  const scrolling = s.trigger === 'scroll'
  return (
    <div className="min-h-screen w-full bg-background px-6 py-16 text-foreground md:px-10">
      <div className="mx-auto max-w-6xl">
        {scrolling ? (
          <p className="flex h-[65svh] items-end pb-10 font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Scroll down: the prints develop as they rise</p>
        ) : null}
        <header className="mb-10 flex flex-wrap items-end justify-between gap-6">
          <div>
            <p className="font-mono text-[11px] uppercase tracking-[0.18em] text-muted-foreground">Ink reveal · {HINT[s.trigger] ?? HINT.hover}</p>
            <h2 className="mt-3 font-serif text-4xl leading-tight tracking-[-0.01em] md:text-5xl">Four prints, developed on contact.</h2>
          </div>
          <p className="max-w-xs text-sm text-muted-foreground">
            {s.paired
              ? 'Each print rests on its partner and the other inks in over it. All four cards draw through one WebGL context.'
              : 'The ink blooms from where you come in and pools at its edge. All four cards draw through one WebGL context.'}
          </p>
        </header>
        <ul className="grid grid-cols-2 gap-4 md:grid-cols-4 md:gap-6">
          {WORKS.map((w, i) => (
            <li key={w.slug}>
              <a
                id={w.slug}
                href={`#${w.slug}`}
                className="group block rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-4 focus-visible:ring-offset-background"
              >
                <InkReveal
                  src={props.src ?? w.src}
                  restSrc={s.paired && !props.src ? WORKS[PARTNER[i]!]!.src : undefined}
                  alt={w.alt}
                  trigger={s.trigger as Trigger}
                  origin={s.origin as Origin}
                  ease={s.ease}
                  duration={s.duration}
                  progress={s.progress}
                  rest={s.rest}
                  feather={s.feather}
                  grain={s.grain}
                  noiseScale={s.noiseScale}
                  inkColor={s.inkColor}
                  aspectRatio="3 / 4"
                  className="rounded-lg bg-background"
                />
                <p className="mt-3 font-serif text-lg leading-snug">{w.title}</p>
                <p className="text-xs text-muted-foreground">{w.by}</p>
              </a>
            </li>
          ))}
        </ul>
        <p className="mt-10 text-xs text-muted-foreground">All four works are in the public domain. Images from Wikimedia Commons.</p>
        {scrolling ? <div className="h-[60svh]" /> : null}
      </div>
    </div>
  )
}

More by Ryan Tseng

View all
  • Ink Hero

    Ink Hero

    77
  • Ink Ridges

    Ink Ridges

    63
  • Ink Headline

    Ink Headline

    39
  • Ease Atlas

    Ease Atlas

More in Cards

View all
  • Statistics Card 2

    Statistics Card 2

    263
  • Stats cards with links

    Stats cards with links

    39
  • Card

    Card

    70
  • Course Design Cards

    Course Design Cards

    1.1K
  • The Item one

    The Item one

    3
  • Card Studio

    Card Studio

    7
  • Statistics Card 7

    Statistics Card 7

    292
  • Item

    Item

    10