useGsap()
useGsap() is auto-imported in every component and composable. It supports two call signatures.
Which form should I use?
- Zero-argument — Quick prototyping or when you need full manual control over cleanup. You manage timelines, contexts, and plugin instances yourself.
- Context form (recommended) — For most components. Automatic cleanup, SSR-safe, page-transition aware, and scoped to your element. No boilerplate.
Zero-argument form
Returns the raw gsap instance. Use this when you want full control and will manage cleanup manually.
<script setup lang="ts">
const gsap = useGsap()
const boxRef = ref<HTMLElement | null>(null)
onMounted(() => {
gsap.from(boxRef.value, { opacity: 0, scale: 0.8, duration: 0.5 })
})
</script>
<template>
<div ref="boxRef" class="box">Hello!</div>
</template>
You can also use the auto-imported gsap global directly — both are equivalent.
Timeline example:
<script setup lang="ts">
const gsap = useGsap()
const boxRef = ref<HTMLElement | null>(null)
onMounted(() => {
const tl = gsap.timeline({ repeat: -1, yoyo: true })
tl.to(boxRef.value, { x: 200, duration: 1, ease: 'power2.inOut' })
.to(boxRef.value, { rotation: 360, duration: 0.8 })
})
</script>
<template>
<div ref="boxRef" class="box" />
</template>
Context form (recommended)
Pass a setup function to wrap your animations in a gsap.context(). The context is automatically reverted when the component unmounts — for pages with a transition, the revert is deferred to Nuxt's page:transition:finish hook so it runs after the fade-out. No cleanup boilerplate needed.
Advantages
- Auto-cleanup — All tweens, timelines, and plugin instances (ScrollTrigger, Draggable, etc.) are reverted automatically without a single
onUnmounted. - SSR-safe — Returns
{ contextSafe: fn => fn }on the server, so no DOM access during SSR builds. - Page-transition aware — when the leaving page has a transition, the revert is explicitly deferred to Nuxt's
page:transition:finishhook, so exit animations play smoothly through the fade-out. - Scoped selectors by default — CSS class/id selectors are scoped to the current component's root element (or the
scopeyou pass), preventing selector leaks to same-class elements in other components. Passscope: nullto match globally. - Reactive re-runs — Pass
dependenciesto re-run the setup function when reactive values change, with automatic context revert + re-run after the DOM update.
Before and after
Without context form (manual cleanup):
const gsap = useGsap()
let ctx: gsap.Context
onMounted(() => {
ctx = gsap.context(() => {
gsap.from('.box', { opacity: 0, y: 30 })
ScrollTrigger.create({ trigger: '.box', start: 'top 80%' })
})
})
onUnmounted(() => ctx.revert()) // must remember this
With context form (recommended):
useGsap(() => {
gsap.from('.box', { opacity: 0, y: 30 })
ScrollTrigger.create({ trigger: '.box', start: 'top 80%' })
// auto-reverted — ScrollTrigger too
}, { scope: containerRef })
<script setup lang="ts">
const containerRef = ref<HTMLElement | null>(null)
useGsap(() => {
gsap.from('.box', { opacity: 0, y: 30, duration: 0.6, ease: 'power2.out' })
}, { scope: containerRef })
</script>
<template>
<div ref="containerRef">
<div class="box">I animate in safely</div>
</div>
</template>
Options
| Option | Type | Default | Description |
|---|---|---|---|
scope | Element | Ref | component ref | null | component root element | Scopes class/id selectors inside the element. Accepts a plain element, template ref, or component ref ($el is used). null opts out (global selectors). |
dependencies | WatchSource[] | [] | Re-run setup when any source changes |
revertOnUpdate | boolean | true | Revert the previous context before re-running |
cleanupOn | 'unmount' | 'route-leave' | 'unmount' | Deprecated, no-op. Cleanup is always transition-aware (see note below). Kept for backward compatibility. |
useGsap(setup) now scopes selectors to the current component's root element by
default (previously selectors matched globally when no scope was passed). If
you relied on global selectors, pass scope: null explicitly.definePageMeta({ pageTransition })
or a global app.pageTransition), useGsap(setup) defers the context revert to
Nuxt's page:transition:finish hook, so animations keep playing through the
fade-out. Without a transition, the revert happens immediately in onUnmounted.
Vue itself flushes unmounted hooks at leave start (see
vuejs/core#994), which is why the
module defers explicitly instead of relying on unmount timing. The deprecated
cleanupOn option is accepted for compatibility, but it does not change runtime
behavior.contextSafe
The setup callback receives a self reference to the GSAP context. contextSafe is returned so you can wrap event handlers that create animations after mount:
const { contextSafe } = useGsap((self) => {
// initial animations
}, { scope: containerRef })
const handleClick = contextSafe(() => {
gsap.to('.box', { rotation: '+=90' })
})
With reactive dependencies
const color = ref('red')
useGsap(() => {
gsap.to('.box', { backgroundColor: color.value, duration: 0.3 })
}, { dependencies: [color] })
Every time color changes the context reverts and re-runs after Vue flushes the DOM update
cycle (controlled by revertOnUpdate).
Using with plugin composables
You can call plugin composables inside the setup function to access static methods (getAll(), refresh(), create()). The key is: call the composable outside useGsap to get the instance, but run animations (and their cleanup) inside the setup function.
With ScrollTrigger
<script setup lang="ts">
const ScrollTrigger = useScrollTrigger()
const containerRef = ref<HTMLElement | null>(null)
useGsap(() => {
gsap.from('.card', {
opacity: 0,
y: 40,
stagger: 0.1,
scrollTrigger: {
trigger: '.card',
start: 'top 85%',
end: 'top 50%',
scrub: true,
},
})
// ScrollTrigger.refresh() available via the composable outside
}, { scope: containerRef })
</script>
<template>
<div ref="containerRef">
<div class="card">Card 1</div>
<div class="card">Card 2</div>
</div>
</template>
refresh() or getAll(). The animations and ScrollTriggers created inside the setup function are what gets auto-reverted when the component unmounts.With Draggable and contextSafe
Event handlers that add animations after mount should be wrapped with contextSafe:
const Draggable = useDraggable()
const { contextSafe } = useGsap(() => {
Draggable.create(boxRef.value, { type: 'x,y' })
})
const handleDragEnd = contextSafe(() => {
gsap.to(boxRef.value, { scale: 1.1, duration: 0.2 })
})
All animations and Draggable instances created inside contextSafe are part of the same context and get auto-reverted.
Zero-arg form: cleanup guide
Use these patterns only when using the zero-argument form of useGsap() and you need manual control. For most components, prefer the context form above.
gsap.context() — covers all child tweens, timelines, and ScrollTriggers:
<script setup lang="ts">
const gsap = useGsap()
let ctx: gsap.Context
onMounted(() => {
ctx = gsap.context(() => {
gsap.to('.box', { x: 100 })
})
})
onUnmounted(() => ctx.revert())
</script>
Single timeline:
const gsap = useGsap()
const tl = gsap.timeline()
onUnmounted(() => tl.kill())
Plugin instance (e.g. Draggable):
const Draggable = useDraggable()
let draggables: ReturnType<typeof Draggable.create> = []
onMounted(() => {
draggables = Draggable.create(boxRef.value)
})
onUnmounted(() => {
draggables.forEach(d => d.kill())
draggables = []
})
gsap.registerPlugin() manually.