---
title: 'Context-Safe & SSR'
excerpt: 'Wrap post-mount callbacks, respect reduced motion, never call gsap during SSR.'
icon: 'Code2'
---

## contextSafe

Animations created **inside click handlers** after `useGSAP` runs are **not** in the context unless wrapped:

<ComponentPreview>
  <GsapContextSafeClick />

  <DocsFileTabs title="Context safe click">

```tsx title="context-safe-click.tsx"
useGSAP((context, contextSafe) => {
  const onClick = contextSafe(() => {
    gsap.fromTo('.btn', { scale: 1 }, { scale: 0.92, yoyo: true, repeat: 1, duration: 0.1 });
  });
  btn.addEventListener('click', onClick);
  return () => btn.removeEventListener('click', onClick);
}, { scope });
```

```html title="context-safe-click-markup.html"
<button class="gsap-bounce">Click for feedback</button>
```

  </DocsFileTabs>
</ComponentPreview>

<Callout variant="dont" title="Leaked tweens">
  A bare `onClick={() => gsap.to(...)}` without `contextSafe` keeps running after unmount and can trigger React warnings.
</Callout>

## prefers-reduced-motion

Respect accessibility — gentler motion, not zero:

<ComponentPreview replay>
  <GsapMatchMediaReduced />

  <DocsFileTabs title="Match media reduced">

```tsx title="match-media-reduced.tsx"
const mm = gsap.matchMedia();
mm.add('(prefers-reduced-motion: no-preference)', () => {
  gsap.from('.banner', { y: 24, autoAlpha: 0, duration: 0.4 });
});
mm.add('(prefers-reduced-motion: reduce)', () => {
  gsap.from('.banner', { autoAlpha: 0, duration: 0.15 }); // fade only
});
return () => mm.revert();
```

```html title="match-media-reduced-markup.html"
<div class="gsap-banner rounded-lg border bg-card px-4 py-3">
  Respects prefers-reduced-motion — fade only when reduced.
</div>
```

  </DocsFileTabs>
</ComponentPreview>

## SSR

GSAP runs in the browser only. Use `'use client'`, `useGSAP`, or `useEffect` — never call `gsap.*` during server render.

Register plugins at **module top level**, not inside components that re-render.
