Circular Reveal
A hidden face that opens in a circle born under the pointer, then follows it while it stays on the card. Mouse, finger and keyboard.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. SVG has 30 effects, including 3 free. Explore the category →
3 usage examples



How it works
The .cvr-card stacks a visible face, .cvr-front, and a face to uncover, .cvr-back. Both take any content: text, a list, a button, or an image with the .cvr-media class, which then covers the whole face. The hidden face is cut by clip-path: circle(0% at var(--cvr-x) var(--cvr-y)): a radius-less circle whose centre comes from two variables.
When the mouse comes in, the script measures the entry point inside the card (as a percentage), moves the circle centre there without animating it, then adds the .cvr-on class: the radius grows to --cvr-size (100% by default) in 0.4 s. So the circle bursts out exactly where the pointer arrives — from the left it opens on the left, from below it opens at the bottom. While hovering, each move updates the centre and the circle smoothly catches up. On leaving, it closes towards the point where the mouse went out.
With a finger, one tap opens the circle where you touched and a second tap closes it; a link or button tapped inside the card keeps its action. With the keyboard, focusing the card (:focus-visible) or a focusable element inside it (:has(:focus-visible)) opens it too, from the last known point (the centre if the mouse never came by).
The script handles every .cvr-card in the page's .fx-0622-root roots, each with its own coordinates set as inline style on the card: several cards in one root, or several copies of the effect, work without getting in each other's way. No name is added to window.
Accessibility
- Keyboard: on Tab the card (
tabindex="0"in the demo) opens and gets a#67e8f9outline. On an<a>or<button>, droptabindex. - Screen readers: the hidden face stays in the page and is read out. When it repeats the visible face (a duplicated menu label, say), put
aria-hidden="true"on.cvr-back. - Reduced motion: under
prefers-reduced-motion: reducethe circle is no longer animated; the hidden face shows at once, whole, then hides at once. - Touch: one tap opens from the finger, a second one closes. A finger sliding to scroll the page triggers nothing.
- Without JavaScript: hover does nothing any more, but keyboard focus still opens the card (from the centre) and the hidden content remains readable by screen readers.
Browser compatibility
Measured in Chromium 151, Firefox 153 and Playwright's WebKit 26.5: 0 errors, two copies on the page (hovering the 2nd copy from the left: circle centre measured 7 to 9% from the left edge in all three engines), Tab, tap that opens then closes, immediate rendering with reduced motion, nothing overflowing at 390 px. :focus-visible and :has() set the minimum versions.
Before those versions, mouse and finger work in older versions that have the inset property (Chrome 87, Firefox 66, Safari 14.1: the versions that brought the inset property used to stack the faces); only keyboard opening is missing.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0622-root">
<div class="cvr-card" tabindex="0">
<span class="cvr-front">Move & Hover</span>
<div class="cvr-back">
<span>Magic!</span>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
--cvr-w |
280px |
Card width, capped by its container (100% for a menu row or a map). |
--cvr-h |
180px |
Card height. |
--cvr-radius |
1rem |
Corner rounding. |
--cvr-front and --cvr-front-color |
#1a1a24 and #fff |
Background and text of the visible face. |
--cvr-back and --cvr-back-color |
linear-gradient(135deg,#06b6d4,#3b82f6) and #fff |
Background and text of the uncovered face; one value per card gives different colours in the same root. |
--cvr-size |
100% |
Final circle radius. At 100%, entering from a corner leaves the opposite corner hidden; 150% always covers everything; 60–75% gives a disc that follows the pointer. |
--cvr-speed |
.4s |
Opening and closing time, and how smoothly the circle catches up with the pointer. |
--cvr-bg and --cvr-min-h |
#0a0a0f and 300px |
Root background and minimum height (transparent and 0 to fit it into a page). |
.cvr-on class |
set by the script | Open state. The script adds it when the pointer enters or on tap; you can add it yourself, the circle then starts from the last known coordinates. |
FAQ
--cvr-size:150% on the root or the card so the circle always covers everything.<img class="cvr-media" src="…" alt="…"> inside .cvr-back. It fills the face (object-fit: cover); text placed after it shows on top. The same works for an SVG, like the price map in the example.