Cards & Grids

Foil Surface

A foil finish that wraps any content: five gradient layers with blend modes shift their sheen with scroll progress and pointer position, without any tilt.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Turn any container into a flowing hot-foil or holographic-sticker surface: badges, membership cards, tickets, headline blocks. The component stacks five gradient layers over your content (colored foil, film blobs, a pearl base, a diagonal shine band and a pointer glare) and blends them in with blend modes. The sheen is driven by scroll progress and pointer position; CSS variables are written in a batched requestAnimationFrame, never through React state and with no transitions. It only handles the surface and never tilts — put it inside tilt-card when you want 3D tilt, or use holographic-card for a ready-made card + tilt + hologram.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/foil-surface.en.json?t=<install token>"

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.8
1
Active edited usageDefault props
<FoilSurface/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add "https://webberui.com/r/foil-surface.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/foil-surface.

Usage

import { FoilSurface } from "@/components/ui/foil-surface";

<FoilSurface className="rounded-full" intensity={0.8}>
  <div className="flex size-40 items-center justify-center rounded-full bg-neutral-100">
    Limited Member 2026
  </div>
</FoilSurface>

// Driven by whole-page scroll progress, ignoring the pointer
<FoilSurface scrollProgressMode="document" interactive={false} className="rounded-xl">
  <MemberCard />
</FoilSurface>

Props

PropTypeDefaultDescription
childrenReact.ReactNode—Any content to wrap; the five foil layers sit on top of it
scrollProgressMode"element" | "document""element"element: progress of the element through the viewport; document: whole-page scroll progress
intensitynumber0.8Sheen strength (0–1), multiplied into every layer's opacity
shineSpeednumber1How fast the sheen flows while scrolling, multiplied into the scroll coefficients (0.82 and 80°)
interactivebooleantrueWhether the glare follows the pointer; when false only scroll drives the sheen
as"div" | "span""div"Root element tag; use span for inline content (rendered as inline-block)
layerClassNamestring—Class applied to every foil layer
classNamestring—Passed to the root element; set the radius here, the five layers inherit it via rounded-[inherit]

How it works

  • Update loop: listens to window scroll (in the capture phase, so nested scroll containers are caught too), resize, and the element's pointermove / pointerleave, all passive. Events only record the latest values and queue a single rAF, so several events in one frame collapse into one write
  • Scroll progress: element mode is (viewport height − rect.top) ÷ (viewport height + rect.height), clamped to 0–1; document mode is scrollTop ÷ (scrollHeight − clientHeight). element mode always measures against the browser viewport, so inside a short nested scroll container the change is smaller — raise shineSpeed to compensate
  • Pointer: 0–1 coordinates relative to the element, clamped to 0.08–0.92; on leave it returns to the center
  • CSS variables written: --foil-shift, --foil-y-shift, --glare-x, --glare-y, --shine-angle, --shine-opacity, set directly on the root element's style so children or a custom layerClassName can use them
  • Five layers: foil (seven-color linear gradient at 300% size panned by the variables, boosted saturate / contrast), film (four colored blobs, screen), pearl (pearly white, soft-light), shine (diagonal band whose angle turns with scroll, screen) and glare (round pointer highlight). The root is isolate, so blend modes only mix with this component's content
  • Paused off-screen: when IntersectionObserver reports the element is out of view, rAF writes stop; one write is made when it comes back
  • Versus holographic-card: holographic-card is a card + 3D tilt; foil-surface is a surface finish for any container, scroll-driven and never tilted. Put foil-surface inside tilt-card to combine them

Accessibility

  • All five foil layers are aria-hidden and pointer-events-none, so they never affect reading order, clicks or focus
  • The foil is purely visual and carries no information; its strength is capped by intensity, but you still need to ensure your text has enough contrast
  • When the user has enabled reduce motion, no scroll or pointer listeners are attached and the sheen is pinned to a static middle state (scroll 0.5, pointer centered)