# 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.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: Effects
- Tags: animation, reveal, scroll, stagger, entrance, micro-interaction
- Types: `RevealProps`
- Docs: https://fancy-ui.rama.app/docs/components/reveal
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/reveal

## Import

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

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { Reveal } from 'fancy-ui-react';` (`class` becomes `className`, snippets become `ReactNode`, DOM callbacks are camel-cased; exact names on https://fancy-ui.rama.app/docs/components/react/reveal.md).

## 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 |

## Snippets

| Snippet | Description |
| --- | --- |
| `children` | Content to reveal |

## Examples

### Basic 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>
```

### Stagger

Cascading reveal timed from the center outward.

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

	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 "fancy-ui-svelte";
	import { Button } from "fancy-ui-svelte";

	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>
```
