v-reveal Directive
v-reveal — toggles a class (default "in") and/or a data-attribute on the bound element as it enters the viewport, without calling useElementVisibility() yourself. A directive is DOM-level, not bound to setup() the way a composable is, so it works directly inside v-for — including items rendered after an async fetch, where a composable can't be called per iteration.
<template>
<div v-for="item in items" :key="item.id" v-reveal.once class="card">
{{ item.title }}
</div>
</template>v-reveal.once is shorthand for v-reveal="{ once: true }". Pass an object for the rest:
<div v-reveal="{ once: true, threshold: 0.3, class: 'visible', onEnter: (info) => track(item.id) }">A fresh inline options object on every re-render — the common case, since v-reveal="{ once: true, onEnter: ... }" builds a new object literal each time — doesn't force a re-subscribe: class/attribute/once/ threshold/rootMargin/root are compared by value (a threshold array by its joined contents, not by reference), and onEnter/onLeave identity is ignored entirely. The directive only unbinds and rebinds when one of those values actually changed.
Binding value
class
string | null · default: 'in'
Pass null to disable the class toggle entirely (e.g. if you only want attribute).
attribute
string | null · default: null
A boolean data-attribute set alongside class (present while intersecting, removed otherwise) — for styling purely by attribute selector.
once
boolean · default: viewportDefaults.once, or true if the .once modifier is present
threshold
number | number[] · default: viewportDefaults.threshold
rootMargin
string · default: viewportDefaults.rootMargin
root
HTMLElement | null · default: null
onEnter / onLeave
(info: IntersectionInfo) => void · default: —
Same IntersectionInfo shape as Visibility Engine.
All of the above fall back to viewportDefaults the same way useElementVisibility does — set them once with setViewportDefaults() instead of repeating options on every v-reveal.
Modifiers
.once
Shorthand for once: true — equivalent to v-reveal="{ once: true }", but an explicit once in the object value always wins if both are given.
Example:
<script setup lang="ts">
const cards = [{ id: 1, title: 'A' } /* ...rendered by a v-for, possibly after a fetch */]
</script>
<template>
<div
v-for="card in cards"
:key="card.id"
v-reveal="{ once: true, threshold: 0.2 }"
class="reveal"
>
{{ card.title }}
</div>
</template>.reveal {
opacity: 0;
transition: opacity 0.3s;
}
.reveal.in {
opacity: 1;
}When you'd reach for this instead
- A single, already-known element — plain
useElementVisibility()is simpler when there's nov-forinvolved. - The reactive
isVisiblevalue needs to drive more than a class (e.g. conditionally mounting a heavy child) — see<InView>, which exposes it via a scoped slot. - A whole page (or a container) of markup you don't want to annotate with a directive/composable at all — see Reveal Controller, a single framework-agnostic call that finds matching elements on its own, including ones added later.