Effects Fancy Stable
Reveal
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
Preview
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>Installation
pnpm add fancy-ui-svelte
import { Reveal } from 'fancy-ui-svelte'
Usage
svelte
<script lang="ts">
import { Reveal } from 'fancy-ui-svelte';
</script>
<Reveal />Examples
Basic Usage
svelte
<script lang="ts">
import { Reveal } from "$lib/fancy-ui/reveal";
import { Button } from "$lib/fancy-ui/button";
// 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>Stagger
Cascading reveal timed from the center outward.
svelte
<script lang="ts">
import { Reveal } from "$lib/fancy-ui/reveal";
const tags = ["Design", "Motion", "Layout", "Type", "Color", "Sound", "Depth"];
</script>
<div class="w-full p-6">
<!-- stagger widened from the 50ms ladder step so the cascade reads at preview size -->
<Reveal trigger="mount" stagger={60} from="center" class="flex flex-wrap justify-center gap-2">
{#each tags as tag (tag)}
<span
class="bg-muted text-muted-foreground border-border rounded-full border px-3 py-1 text-sm"
>
{tag}
</span>
{/each}
</Reveal>
</div>Manual Trigger
Drive the reveal from any external boolean, not the viewport.
svelte
<script lang="ts">
import { Reveal } from "$lib/fancy-ui/reveal";
import { Button } from "$lib/fancy-ui/button";
let active = $state(false);
</script>
<div class="flex w-full flex-col items-start gap-4 p-6">
<Button variant="outline" size="sm" onclick={() => (active = !active)}>
{active ? "Hide details" : "Show details"}
</Button>
<Reveal
trigger="manual"
{active}
preset="fade-up"
class="bg-card border-border w-full rounded-lg border p-4"
>
<p class="text-muted-foreground text-sm">
`trigger="manual"` mirrors the `active` prop directly — no viewport or mount timing involved,
so any external state can drive the reveal.
</p>
</Reveal>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
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 | boolean | false | Read only when trigger="manual" — true reveals, false re-arms |
once | boolean | true | Disconnects the observer after the first reveal; false re-arms on leaving the viewport (trigger="view" only) |
threshold | number | 0.1 | IntersectionObserver threshold (trigger="view" only) |
rootMargin | string | "0px 0px -10% 0px" | IntersectionObserver rootMargin (trigger="view" only) |
duration | number | 600 | Entrance duration in ms |
delay | number | 0 | Delay before the entrance starts, in ms |
easing | string | "cubic-bezier(0.16, 1, 0.3, 1)" | CSS easing for the entrance |
distance | number | 16 | Travel distance in px for the four directional presets — ignored by scale |
stagger | number | 0 | Milliseconds 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 | null | null | Bindable reference to the root element |
Slots
| Slot | Description |
|---|---|
children | Content to reveal |
Links
Related components
An
AnimatedBeam
Animated SVG beams connecting elements with smooth gradients
Effects Stable
Bo
BorderBeam
Gradient beam that races around a container's border using a CSS offset-path animation, masked so only the border ring is painted, with configurable size, speed, anchor position, and gradient colors
Effects Stable
Co
Confetti
Confetti celebration effect powered by canvas-confetti with button trigger support
Effects Stable