Documentation

Documentation

React

Base UI

Integrate Motion with Base UI

Base UI is a component library for React that's rapidly growing in popularity. It's possible to animate almost all Base UI components with Motion, and in this guide we'll explore how.

Setup motion components

By default, Base UI components render and control their own DOM elements. However most components provide the render prop that allows you to switch this out for a motion component.

import { Menu } from "@base-ui-components/react/menu"
import { motion } from "motion/react"

function Component() {
  return (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        whilePress={{ scale: 0.9 }}
      />
    } />
  )
}

Exit animations

In most situations, you can animate Base UI components as they leave the DOM using AnimatePresence and the exit prop, as usual:

<AnimatePresence>
  {open && (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    } />
  )}
</AnimatePresence>

However, some Base UI components like ContextMenu and Popover control this conditional rendering themselves. To add exit animations to these components, we must:

  • Hoist their open state

  • Add keepMounted to their Portal component

  • Conditionally render the Portal component with AnimatePresence

A component's open state can be hoisted by defining it manually with useState:

const [open, setOpen] = useState(false)

return (
  <ContextMenu.Root open={open} onOpenChange={setOpen}>

Then, conditionally render the Portal (with a keepMounted prop) as a child of AnimatePresence:

return (
   <ContextMenu.Root open={open} onOpenChange={setOpen}>
    <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
    <AnimatePresence>
      {open && (
        <ContextMenu.Portal keepMounted>

We can then add an exit animation via a motion component rendered via a render prop:

function App() {
  const [open, setOpen] = useState(false)
  
  return (
    <ContextMenu.Root open={open} onOpenChange={setOpen}>
      <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
      <AnimatePresence>
        {open && (
          <ContextMenu.Portal keepMounted>
            <ContextMenu.Positioner>
              <ContextMenu.Popup
                render={
                  <motion.div
                    initial={{ opacity: 0, transform: "scale(0.9)" }}
                    animate={{ opacity: 1, transform: "scale(1)" }}
                    exit={{ opacity: 0, transform: "scale(0.9)" }}
                  />
                }
              >
                {/* Children */}
              </ContextMenu.Popup>

Note: Portal will keep the tree mounted as long as Base UI detects animations on an element using element.getAnimations(). Motion will run opacity, transform, filter, and clipPath animations via hardware acceleration, so ensure at least one of these values is used for the exit animation.

Examples

Motion+ unlocks the source code to 175+ additional Motion examples. It's a one-time payment, lifetime update membership that also unlocks a creative animation library containing components like Cursor and AnimateNumber.

13 of these examples showcase Motion x Base UI:

Motion+ also includes a custom LLM ruleset for Motion x Base UI to improve the way your AI editor will integrate the two libraries.

Base UI is a component library for React that's rapidly growing in popularity. It's possible to animate almost all Base UI components with Motion, and in this guide we'll explore how.

Setup motion components

By default, Base UI components render and control their own DOM elements. However most components provide the render prop that allows you to switch this out for a motion component.

import { Menu } from "@base-ui-components/react/menu"
import { motion } from "motion/react"

function Component() {
  return (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        whilePress={{ scale: 0.9 }}
      />
    } />
  )
}

Exit animations

In most situations, you can animate Base UI components as they leave the DOM using AnimatePresence and the exit prop, as usual:

<AnimatePresence>
  {open && (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    } />
  )}
</AnimatePresence>

However, some Base UI components like ContextMenu and Popover control this conditional rendering themselves. To add exit animations to these components, we must:

  • Hoist their open state

  • Add keepMounted to their Portal component

  • Conditionally render the Portal component with AnimatePresence

A component's open state can be hoisted by defining it manually with useState:

const [open, setOpen] = useState(false)

return (
  <ContextMenu.Root open={open} onOpenChange={setOpen}>

Then, conditionally render the Portal (with a keepMounted prop) as a child of AnimatePresence:

return (
   <ContextMenu.Root open={open} onOpenChange={setOpen}>
    <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
    <AnimatePresence>
      {open && (
        <ContextMenu.Portal keepMounted>

We can then add an exit animation via a motion component rendered via a render prop:

function App() {
  const [open, setOpen] = useState(false)
  
  return (
    <ContextMenu.Root open={open} onOpenChange={setOpen}>
      <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
      <AnimatePresence>
        {open && (
          <ContextMenu.Portal keepMounted>
            <ContextMenu.Positioner>
              <ContextMenu.Popup
                render={
                  <motion.div
                    initial={{ opacity: 0, transform: "scale(0.9)" }}
                    animate={{ opacity: 1, transform: "scale(1)" }}
                    exit={{ opacity: 0, transform: "scale(0.9)" }}
                  />
                }
              >
                {/* Children */}
              </ContextMenu.Popup>

Note: Portal will keep the tree mounted as long as Base UI detects animations on an element using element.getAnimations(). Motion will run opacity, transform, filter, and clipPath animations via hardware acceleration, so ensure at least one of these values is used for the exit animation.

Examples

Motion+ unlocks the source code to 175+ additional Motion examples. It's a one-time payment, lifetime update membership that also unlocks a creative animation library containing components like Cursor and AnimateNumber.

13 of these examples showcase Motion x Base UI:

Motion+ also includes a custom LLM ruleset for Motion x Base UI to improve the way your AI editor will integrate the two libraries.

Base UI is a component library for React that's rapidly growing in popularity. It's possible to animate almost all Base UI components with Motion, and in this guide we'll explore how.

Setup motion components

By default, Base UI components render and control their own DOM elements. However most components provide the render prop that allows you to switch this out for a motion component.

import { Menu } from "@base-ui-components/react/menu"
import { motion } from "motion/react"

function Component() {
  return (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        whilePress={{ scale: 0.9 }}
      />
    } />
  )
}

Exit animations

In most situations, you can animate Base UI components as they leave the DOM using AnimatePresence and the exit prop, as usual:

<AnimatePresence>
  {open && (
    <Menu.Trigger render={
      <motion.button
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
    } />
  )}
</AnimatePresence>

However, some Base UI components like ContextMenu and Popover control this conditional rendering themselves. To add exit animations to these components, we must:

  • Hoist their open state

  • Add keepMounted to their Portal component

  • Conditionally render the Portal component with AnimatePresence

A component's open state can be hoisted by defining it manually with useState:

const [open, setOpen] = useState(false)

return (
  <ContextMenu.Root open={open} onOpenChange={setOpen}>

Then, conditionally render the Portal (with a keepMounted prop) as a child of AnimatePresence:

return (
   <ContextMenu.Root open={open} onOpenChange={setOpen}>
    <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
    <AnimatePresence>
      {open && (
        <ContextMenu.Portal keepMounted>

We can then add an exit animation via a motion component rendered via a render prop:

function App() {
  const [open, setOpen] = useState(false)
  
  return (
    <ContextMenu.Root open={open} onOpenChange={setOpen}>
      <ContextMenu.Trigger>Open menu</ContextMenu.Trigger>
      <AnimatePresence>
        {open && (
          <ContextMenu.Portal keepMounted>
            <ContextMenu.Positioner>
              <ContextMenu.Popup
                render={
                  <motion.div
                    initial={{ opacity: 0, transform: "scale(0.9)" }}
                    animate={{ opacity: 1, transform: "scale(1)" }}
                    exit={{ opacity: 0, transform: "scale(0.9)" }}
                  />
                }
              >
                {/* Children */}
              </ContextMenu.Popup>

Note: Portal will keep the tree mounted as long as Base UI detects animations on an element using element.getAnimations(). Motion will run opacity, transform, filter, and clipPath animations via hardware acceleration, so ensure at least one of these values is used for the exit animation.

Examples

Motion+ unlocks the source code to 175+ additional Motion examples. It's a one-time payment, lifetime update membership that also unlocks a creative animation library containing components like Cursor and AnimateNumber.

13 of these examples showcase Motion x Base UI:

Motion+ also includes a custom LLM ruleset for Motion x Base UI to improve the way your AI editor will integrate the two libraries.

Level up your animations with Motion+

More than 150+ Motion examples, exclusive APIs like Cursor, private Discord and GitHub, and powerful VS Code animation editing tools.

One-time payment, lifetime updates.

Stay in the loop

Subscribe for the latest news & updates.

Stay in the loop

Subscribe for the latest news & updates.