Skip to content
Effects Fancy Stable

Reveal

.md

Content-agnostic entrance animation triggered by scroll, mount, or a manual switch, with six directional/scale presets and an arbitrary-length JS-computed stagger for direct children

Available for Svelte 5, React, and Vue 3.

Preview

Installation

pnpm add fancy-ui-svelte
import { Reveal } from "fancy-ui-svelte";

Usage

svelte
<script lang="ts">
	import { Reveal } from "fancy-ui-svelte";
	import { Button } from "fancy-ui-svelte";

	// Remounting Reveal (via {#key}) re-plays trigger="mount"'s next-frame
	// reveal from scratch — the simplest way to give this preview a working
	// "Replay" button without reaching for trigger="manual" timing tricks.
	let replayKey = $state(0);
</script>

<div class="flex w-full flex-col gap-4 p-6">
	<div class="flex justify-end">
		<Button variant="outline" size="sm" onclick={() => replayKey++}>Replay</Button>
	</div>

	{#key replayKey}
		<!-- stagger widened from the 50ms ladder step so the cascade reads at preview size -->
		<Reveal trigger="mount" stagger={80} class="grid grid-cols-1 gap-4 sm:grid-cols-3">
			<div class="bg-card border-border rounded-lg border p-4">
				<h3 class="text-foreground text-sm font-semibold">Fast setup</h3>
				<p class="text-muted-foreground mt-1 text-sm">
					Drop it around any content, no config required.
				</p>
			</div>
			<div class="bg-card border-border rounded-lg border p-4">
				<h3 class="text-foreground text-sm font-semibold">Accessible by default</h3>
				<p class="text-muted-foreground mt-1 text-sm">
					Keyboard focus and reduced motion are both handled for you.
				</p>
			</div>
			<div class="bg-card border-border rounded-lg border p-4">
				<h3 class="text-foreground text-sm font-semibold">Themeable</h3>
				<p class="text-muted-foreground mt-1 text-sm">
					Every timing and distance is a CSS variable.
				</p>
			</div>
		</Reveal>
	{/key}
</div>

Examples

Basic Usage

Stagger

Cascading reveal timed from the center outward.

Manual Trigger

Drive the reveal from any external boolean, not the viewport.

Props

PropTypeDefaultDescription
preset "fade" | "fade-up" | "fade-down" | "fade-left" | "fade-right" | "scale""fade-up"Which directional/scale look to animate with
trigger "view" | "mount" | "manual""view"What starts the reveal — viewport, next frame after mount, or the active prop
active booleanfalseRead only when trigger="manual" — true reveals, false re-arms
once booleantrueDisconnects the observer after the first reveal; false re-arms on leaving the viewport (trigger="view" only)
threshold number0.1IntersectionObserver threshold (trigger="view" only)
rootMargin string"0px 0px -10% 0px"IntersectionObserver rootMargin (trigger="view" only)
duration number600Entrance duration in ms
delay number0Delay before the entrance starts, in ms
easing string"cubic-bezier(0.16, 1, 0.3, 1)"CSS easing for the entrance
distance number16Travel distance in px for the four directional presets — ignored by scale
stagger number0Milliseconds per stagger step; 0 animates the root, any positive value animates direct element children instead
from "first" | "last" | "center" | number"first"Where the stagger counts distance from — only meaningful when stagger is greater than 0
initial "hidden" | "visible""hidden"Server-rendered starting state: "hidden" paints the content hidden until it reveals (no flash); "visible" paints it visible and hides it on mount, accepting a flash in exchange for no-JS and non-hydrated safety
as keyof HTMLElementTagNameMap"div"The element tag Reveal renders as, via <svelte:element>
onReveal () => void-Fires once per reveal, the moment the state reaches visible — every re-reveal when once is false
class string-Additional CSS classes, merged onto the root
ref HTMLElement | nullnullBindable reference to the root element

Slots

SlotDescription
children Content to reveal