Presence
Animate component mount and unmount with CSS animations or transitions.
The Presence component enables smooth entry and exit animations for components when they are added to or removed from the DOM. It coordinates the unmounting lifecycle to ensure exit animations complete before the element is removed.
Installation
npm install @zayne-labs/ui-reactPreview
Loading...
Basic Usage
Animations are the default and recommended way to use Presence.
import { Presence } from "@zayne-labs/ui-react/common/presence";
function FadeBox({ isOpen }) {
return (
<Presence present={isOpen}>
<div className="rounded-lg bg-zu-primary p-6 text-white shadow-xl animate-in fade-in duration-300 data-[animation-phase=exit]:animate-out data-[animation-phase=exit]:fade-out">
Hello, I am animated!
</div>
</Presence>
);
}Use variant="transition" when you prefer using CSS transitions over keyframe animations.
<Presence present={isOpen} variant="transition">
<div className="transition-all duration-300 opacity-0 scale-95 data-[transition-phase=enter]:opacity-100 data-[transition-phase=enter]:scale-100">
Smooth transition
</div>
</Presence>Component Source
"use client";import { useComposeRefs } from "@zayne-labs/toolkit-react";import { isFunction, type UnknownObject } from "@zayne-labs/toolkit-type-helpers";import { Slot } from "../slot";import { usePresence, type UsePresenceOptions, type UsePresenceResult } from "./use-presence";type RefProp = { ref?: React.Ref<HTMLElement> };type RenderPropContext = Omit<UsePresenceResult, "propGetters" | "ref"> & Pick<UsePresenceOptions, "present">;export type PresenceProps = UsePresenceOptions & { children?: React.ReactElement<RefProp> | ((props: RenderPropContext) => React.ReactElement<RefProp>); className?: string; forceMount?: boolean;};function Presence(props: PresenceProps) { const { children, className, forceMount = false, onExitComplete, present, variant } = props; const { isMounted, isTransitionComplete, propGetters, ref: presenceRef, } = usePresence({ onExitComplete, present, variant }); const context = { isMounted, isTransitionComplete, present, } satisfies RenderPropContext; const resolvedChild = isFunction(children) ? children(context) : children; const childRef = (resolvedChild?.props.ref ?? (resolvedChild as unknown as UnknownObject).ref) as React.Ref<HTMLElement>; const ref = useComposeRefs(presenceRef, childRef); const shouldRender = forceMount || (variant === "transition" ? isMounted || isTransitionComplete : isMounted); if (!shouldRender) { return null; } return ( <Slot.Root ref={ref} {...propGetters.getPresenceProps({ className })}> {resolvedChild} </Slot.Root> );}export { Presence };Component API
Data Attributes
Presence applies stateful attributes to the child element, allowing you to trigger CSS animations.
| Attribute | Value | Availability | Description |
|---|---|---|---|
data-mounted | true | false | Always | Whether the component is currently mounted in the DOM. |
data-present | true | false | Always | Matches the present prop value. |
data-animation-phase | "enter" | "exit" | variant="animation" only | Current animation phase. |
data-transition-phase | "enter" | "exit" | variant="transition" only | Current transition phase. |
data-variant | "animation" | "transition" | Always | The variant being used. |
Tailwind Selection
You can target these states efficiently in Tailwind CSS:
<div className="data-[animation-phase=exit]:animate-out data-[animation-phase=exit]:fade-out" />Examples
Modal Backstop Pattern
<Presence present={isOpen}>
<div className="fixed inset-0 animate-in bg-black/50 backdrop-blur-sm fade-in data-[animation-phase=exit]:animate-out data-[animation-phase=exit]:fade-out" />
</Presence>The Presence component is built on top of the battle-tested Radix UI presence logic, ensuring it
handles browser edge cases and animation cancellations perfectly.
Last updated on