{"slug":"presence","title":"Presence","description":"Supporting exit animations using the presence machine.","contentType":"component","content":"Presence helps manage mount/unmount transitions with exit animations.\n\nWhen a component is hidden or removed, the DOM usually removes it immediately.\nPresence keeps it mounted long enough for exit animations to finish.\n\n> The presence machine requires using **CSS animations** to animate the\n> component's exit.\n\n## Resources\n\n\n[Latest version: v1.42.0](https://www.npmjs.com/package/@zag-js/presence)\n[Logic Visualizer](https://zag-visualizer.vercel.app/presence)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/presence)\n\n\n\n## Installation\n\nInstall the presence package:\n\n**React**\n\n```bash\nnpm install @zag-js/presence @zag-js/react\n# or\nyarn add @zag-js/presence @zag-js/react\n```\n\n**Solid**\n\n```bash\nnpm install @zag-js/presence @zag-js/solid\n# or\nyarn add @zag-js/presence @zag-js/solid\n```\n\n**Vue**\n\n```bash\nnpm install @zag-js/presence @zag-js/vue\n# or\nyarn add @zag-js/presence @zag-js/vue\n```\n\n**Svelte**\n\n```bash\nnpm install @zag-js/presence @zag-js/svelte\n# or\nyarn add @zag-js/presence @zag-js/svelte\n```\n\n## Usage\n\nImport the presence package:\n\n```tsx\nimport * as presence from \"@zag-js/presence\"\n```\n\nThe presence package exports two key functions:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\nThen use the framework integration helpers:\n\n**React**\n\n```tsx\nimport * as presence from \"@zag-js/presence\"\nimport { useMachine, normalizeProps } from \"@zag-js/react\"\n\ninterface PresenceProps {\n  present: boolean\n  unmountOnExit?: boolean\n  onExitComplete?: () => void\n}\n\nfunction Presence(props: PresenceProps) {\n  const { unmountOnExit, present, onExitComplete, ...rest } = props\n\n  const service = useMachine(presence.machine, {\n    present,\n    onExitComplete,\n  })\n\n  const api = presence.connect(service, normalizeProps)\n\n  if (!api.present && unmountOnExit) return null\n\n  return (\n    <div\n      hidden={!api.present}\n      data-state={api.skip ? undefined : present ? \"open\" : \"closed\"}\n      ref={api.setNode}\n      {...rest}\n    />\n  )\n}\n```\n\n**Solid**\n\n```tsx\nimport * as presence from \"@zag-js/presence\"\nimport { useMachine, normalizeProps } from \"@zag-js/solid\"\nimport { createMemo, createUniqueId, splitProps, Show, JSX } from \"solid-js\"\n\ninterface PresenceProps {\n  present: boolean\n  unmountOnExit?: boolean\n  onExitComplete?: () => void\n  children: JSX.Element\n}\n\nfunction Presence(props: PresenceProps) {\n  const [machineProps, localProps] = splitProps(props, [\n    \"present\",\n    \"unmountOnExit\",\n    \"onExitComplete\",\n  ])\n\n  const service = useMachine(presence.machine, machineProps)\n\n  const api = createMemo(() => presence.connect(service, normalizeProps))\n  const unmount = createMemo(() => !api().present && localProps.unmountOnExit)\n\n  return (\n    <Show when={!unmount()}>\n      <div\n        hidden={!api().present}\n        data-state={api().skip ? undefined : present ? \"open\" : \"closed\"}\n        ref={api().setNode}\n        {...localProps}\n      />\n    </Show>\n  )\n}\n```\n\n**Vue**\n\n```html\n<script setup lang=\"ts\">\n  import * as presence from \"@zag-js/presence\"\n  import { useMachine, normalizeProps } from \"@zag-js/vue\"\n  import { computed, watch, ref } from \"vue\"\n\n  const props = defineProps<{\n    present: boolean\n    unmountOnExit?: boolean\n  }>()\n\n  const emit = defineEmits<{\n    (e: \"exit-complete\"): void\n  }>()\n\n  const service = useMachine(presence.machine, {\n    get present() {\n      return props.present\n    },\n    onExitComplete: () => emit(\"exit-complete\"),\n  })\n\n  const api = computed(() => presence.connect(service, normalizeProps))\n\n  const nodeRef = ref<HTMLElement | null>(null)\n\n  watch(nodeRef, () => {\n    api.value.setNode(nodeRef.value)\n  })\n\n  const unmount = computed(() => !api.value.present && unmountOnExit)\n</script>\n\n<template>\n  <div\n    v-show=\"!unmount\"\n    :hidden=\"!api.present\"\n    :data-state=\"api.skip ? undefined : present ? 'open' : 'closed'\"\n    ref=\"nodeRef\"\n    v-bind=\"$attrs\"\n  />\n</template>\n```\n\n**Svelte**\n\n```svelte\n<script lang=\"ts\">\n  import * as presence from \"@zag-js/presence\"\n  import { normalizeProps, useMachine } from \"@zag-js/svelte\"\n  import type { Snippet } from \"svelte\"\n\n  interface Props {\n    present: boolean\n    unmountOnExit?: boolean\n    onExitComplete?: () => void\n    children?: Snippet\n  }\n\n  let { present, unmountOnExit, onExitComplete, children }: Props = $props()\n\n  const service = useMachine(presence.machine, () => ({\n    present,\n    onExitComplete,\n  }))\n  const api = $derived(presence.connect(service, normalizeProps))\n\n  function setNode(node: HTMLDivElement) {\n    api.setNode(node)\n  }\n\n  const unmount = $derived(!api.present && unmountOnExit)\n</script>\n\n{#if !unmount}\n  <div\n    {@attach setNode}\n    hidden={!api.present}\n    data-state={api.skip ? undefined : present ? \"open\" : \"closed\"}\n  >\n    {@render children?.()}\n  </div>\n{/if}\n```\n\n### Running code after exit animation\n\nUse `onExitComplete` to run logic after the exit animation finishes.\n\n```tsx\nconst service = useMachine(presence.machine, {\n  present: open,\n  onExitComplete() {\n    console.log(\"Exit animation finished\")\n  },\n})\n```\n\n### Applying presence changes immediately\n\nSet `immediate` to `true` to skip deferring present-state changes to the next\nframe.\n\n```tsx\nconst service = useMachine(presence.machine, {\n  present: open,\n  immediate: true,\n})\n```\n\n## Styling guide\n\nTo style any entry and exit animations, set up the `@keyframes` and apply the\nanimations.\n\n```css\n@keyframes enter {\n  from {\n    scale: 0.9;\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n    scale: 1;\n  }\n}\n\n@keyframes exit {\n  to {\n    opacity: 0;\n    scale: 0.9;\n  }\n}\n\n[data-state=\"open\"] {\n  animation: enter 0.15s ease-out;\n}\n\n[data-state=\"closed\"] {\n  animation: exit 0.1s ease-in;\n}\n```\n\nYou can then use the `Presence` component in your project.\n\n```tsx\nfunction Example() {\n  const [open, setOpen] = React.useState(true)\n  return (\n    <>\n      <button onClick={() => setOpen((c) => !c)}>Toggle</button>\n      <Presence present={open} unmountOnExit>\n        <div>Content</div>\n      </Presence>\n    </>\n  )\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe presence machine exposes the following context properties:\n\n**`present`**\nType: `boolean | undefined`\nDescription: Whether the node is present (controlled by the user)\n\n**`onExitComplete`**\nType: `VoidFunction | undefined`\nDescription: Function called when the animation ends in the closed state\n\n**`immediate`**\nType: `boolean | undefined`\nDescription: Whether to synchronize the present change immediately or defer it to the next frame\n\n### Machine API\n\nThe presence `api` exposes the following methods:\n\n**`skip`**\nType: `boolean`\nDescription: Whether the animation should be skipped.\n\n**`present`**\nType: `boolean`\nDescription: Whether the node is present in the DOM.\n\n**`setNode`**\nType: `(node: HTMLElement | null) => void`\nDescription: Function to set the node (as early as possible)\n\n**`unmount`**\nType: `VoidFunction`\nDescription: Function to programmatically unmount the node","package":"@zag-js/presence","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/presence.mdx"}