Selected work

Posters, packaging and a few identities from the last two years.

Sticker cover

A page-transition sheet that rises, swaps the page, pops a sticker and lifts away.

Behaviour

  • The sheet rises while the page drifts up 7rem, your change runs, two frames paint, the sticker pops, then the sheet lifts.
  • A play() while the sheet is up runs under the same cover (latest wins); one during the lift covers again afterwards. { instant: true } drops it in place for back/forward.
  • The sheet is aria-hidden, the page is inert while covered, and focus returns to where it was (or the page box if that element is gone).
  • No router dependency and no global attributes; React never re-renders while it plays.
  • Reduced motion: a quick crossfade, no drift and no sticker.

Props

PropTypeDescription
refRef<StickerCoverHandle>ref.current.play(change, { instant }) plays the cover; resolves once it has lifted.
stickerReactNodePops in at the centre once the sheet is up. None: the sheet lifts as soon as the new content has painted.
durationnumberdefault 0.5Seconds the sheet takes to rise; the lift takes 1.2x.
containedbooleandefault falseCover the nearest relative parent (give it overflow-hidden) instead of the screen.
classNamestringdefault "bg-foreground"Classes for the sheet, e.g. its colour (bg-primary).
pageClassNamestringClasses for the box around children: the page that drifts up and goes inert.

Installation

npx shadcn@latest add https://purrfectui.com/r/sticker-cover.json

How to use

import { StickerCover, type StickerCoverHandle } from "@/components/ui/sticker-cover";

const cover = useRef<StickerCoverHandle>(null);
<StickerCover ref={cover} sticker={<img src="/sticker.webp" alt="" className="size-40" />}>{page}</StickerCover>
<button onClick={() => cover.current?.play(() => setPage("about"))}>About</button>

// Next App Router (in a client layout): resolve once the new route has committed
const router = useRouter();
const path = usePathname();
const done = useRef<() => void>(undefined);
useEffect(() => done.current?.(), [path]);
const go = (href: string) => href !== path && cover.current?.play(() => new Promise<void>((r) => { done.current = r; router.push(href); }));

Source code

Loading…

Credits

Ported from poinu.vercel.app.

License

MIT. Copy it into your project and change it as you like.

0