Presence

Track mount/unmount state for animation purposes with animation-aware exit handling.

Presence

The Presence component wraps content and tracks whether it should be visible or hidden. It delays unmounting while exit animations play, ensuring CSS animations complete before the element is removed from the DOM.

Presence is essential for exit animations. It gives your component time to animate out before unmounting, preventing the "flash away" effect.


Features

  • Tracks present boolean state
  • Delays unmounting until exit animations finish
  • Detects CSS animations via computed styles
  • Supports render function children for animation state control
  • Works with any CSS animation library (CSS animations, Framer Motion, etc.)

Usage

import { Presence } from '@loke/ui/presence';

export default function Example() {
  const [isOpen, setIsOpen] = React.useState(true);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle
      </button>
      <Presence present={isOpen}>
        <div className="animate-in animate-out fade-in fade-out">
          Content with exit animation
        </div>
      </Presence>
    </div>
  );
}

Props

Prop

Type


Examples

This content is managed by Presence. It will be unmounted from the DOM when hidden (after any exit animations complete).

Content is: mounted and visible

Basic animation

<Presence present={isOpen}>
  <div className="transition-opacity duration-200" style={{ opacity: isOpen ? 1 : 0 }}>
    Content that animates in/out
  </div>
</Presence>

Render function for animation state

<Presence present={isOpen}>
  {({ present }) => (
    <div
      className={present ? 'opacity-100' : 'opacity-0'}
      style={{ transition: 'opacity 200ms' }}
    >
      Render function lets you control animation differently
    </div>
  )}
</Presence>

With CSS animations

<Presence present={isOpen}>
  <div
    className={isOpen ? 'animate-fade-in' : 'animate-fade-out'}
    style={{
      animation: isOpen
        ? 'fadeIn 200ms ease-in'
        : 'fadeOut 200ms ease-out forwards',
    }}
  >
    CSS animation content
  </div>
</Presence>

How it works

  • When present changes from true to false, Presence checks for exit animations on the element
  • If an animation exists, it waits for the animation to finish before unmounting
  • If no animation exists (or display: none), unmounting happens immediately
  • The render function (if provided) receives present state for coordinating animations

Accessibility

Presence itself has no accessibility implications; it's purely structural. Ensure wrapped content:

  • Remains keyboard-navigable while animating
  • Uses aria-hidden if content is being hidden
  • Maintains focus management when content unmounts

Best practices

  • Always define exit animations when using Presence; otherwise it's unnecessary
  • Combine with DismissableLayer and FocusScope for complete modals
  • Keep animations short (200–300ms) to avoid UX delays
  • Test that animations complete before unmounting in different browsers