# 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-vue` (Vue 3.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/vue/src/components/reveal

## Import

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

Setup (once per project): `npm install fancy-ui-vue`, then put these lines in your main CSS file (the stylesheet imports go right after `@import "tailwindcss";`):

```css
@import "tailwindcss";
@import "fancy-ui-vue/tailwind.css";
@import "fancy-ui-vue/styles.css";
```

Full guide: https://fancy-ui.rama.app/llms.txt

The names below are the ones to write in a Vue template; when one differs from the Svelte name, the Svelte name follows in parentheses. Snippets become named slots (`#default` is the default snippet), a prop the Vue port declares as a model is a `v-model:<name>` (except `ref`, exposed via `defineExpose({ ref })`: put a template ref on the component and read `.ref`) and callbacks are listeners (`onclick` is `@click`). Every component sets `inheritAttrs: false`: `class` is a declared prop, merged where the Svelte component applies it (not always the root element), and only when the props table below lists it; other attributes do not fall through unless the package README lists the component as forwarding native attributes. Divergences: https://github.com/RamaHerbin/fancy-ui/tree/main/vue#divergences-from-the-svelte-api

## 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> |
| `@reveal` (Svelte `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 |
| --- | --- |
| `#default` (Svelte `children`) | Content to reveal |

## Example

```vue
<script setup lang="ts">
import { ref } from "vue";
import { Button, Reveal } from "fancy-ui-vue";

const features = [
	{ title: "Fast setup", body: "Drop it around any content, no config required." },
	{
		title: "Accessible by default",
		body: "Keyboard focus and reduced motion are both handled for you.",
	},
	{ title: "Themeable", body: "Every timing and distance is a CSS variable." },
];

// Changing the key remounts Reveal, which plays the entrance again.
const replayKey = ref(0);
</script>

<template>
	<div class="flex w-full flex-col gap-4 p-6">
		<div class="flex justify-end">
			<Button variant="outline" size="sm" @click="replayKey++">Replay</Button>
		</div>

		<Reveal
			:key="replayKey"
			trigger="mount"
			:stagger="80"
			class="grid grid-cols-1 gap-4 sm:grid-cols-3"
		>
			<div
				v-for="feature in features"
				:key="feature.title"
				class="bg-card border-border rounded-lg border p-4"
			>
				<h3 class="text-foreground text-sm font-semibold">{{ feature.title }}</h3>
				<p class="text-muted-foreground mt-1 text-sm">{{ feature.body }}</p>
			</div>
		</Reveal>
	</div>
</template>
```
