---
meta:
  title: Animated Elements | React Spring
  'og:title': Animated Elements | React Spring
  'twitter:title': Animated Elements | React Spring
  description: An in-depth conceptual guide to animated elements and what they are in React Spring.
  'og:description': An in-depth conceptual guide to animated elements and what they are in React Spring.
  'twitter:description': An in-depth conceptual guide to animated elements and what they are in React Spring.
  'og:url': https://www.react-spring.dev/docs/concepts/animated-elements
  'twitter:url': https://www.react-spring.dev/docs/concepts/animated-elements
sidebar_position: 1
---

import { formatFrontmatterToRemixMeta } from '../helpers/meta'

export const meta = formatFrontmatterToRemixMeta(frontmatter)

# Animated Elements

## What are they?

The basis of `react-spring` depends on two things, `SpringValues` & `animated` components. Let's explore the latter.
An `animated` component receives `SpringValues` through the `style` prop and updates the element without causing a
React render. It works because it is a [Higher-Order component](https://reactjs.org/docs/higher-order-components.html) (HOC)
assigned to a dictionary of elements depending on the target you've selected thus being able to pass the props you
desire for your application but also containing the logic to interpolate the `SpringValues` from the hook you've used.

## Animating elements

The `animated` component can be imported from any of our targets. However, an `animated` component is specific to the target
because it uses the native elements of said target. These native elements are elements that are understood by the `react-reconciler`
of choice: e.g. [`@react-three/fiber`](https://github.com/pmndrs/react-three-fiber) is a reconciler for [`three.js`](https://threejs.org/)
elements. Therefore, any element that the reconciler understands can be animated.

```jsx
import { animated } from '@react-spring/web'

// ✅ This will work because `div` is a web element
<animated.div />

// ❌ This will not work because `mesh` is not a web element.
<animated.mesh />
```

If you used `framer-motion` before, you will most likely be familiar with the dot notation of accessing a dictionary of components.

So, while being able to use `animated.element` is useful, in most cases you'll want to style said element. `react-spring` has no preference about styling techniques:
common techniques like `css modules` or [`tailwind`](https://tailwindcss.com/) all work, because every animated element accepts the properties of the native element, e.g. `className`.

If you're using a CSS-in-JS library to style your components, you'll typically have access to a `styled` function. Just like composing components you can compose
your animated element with the `styled` function:

```jsx
import { styled } from '@stitches/react'

const MyModal = styled(animated.div, {
  width: '40vw',
  height: '20vh',
  borderRadius: '8px',
  backgroundColor: '$white80',
})
```

## Animating components

Sometimes it's impossible to animate an element directly because it comes from another library, like [`radix-ui`](https://www.radix-ui.com/).
Because `animated` is a HOC, we can simply wrap our components with it, assuming the component you're wrapping passes to the `style` prop to the native element.

```jsx
// This comes from another library e.g. radix-ui
const ExternalComponent = props => {
  return <div {...props} />
}

// MyApp.js
import { ExternalComponent } from 'external-library'
import { animated, useSpring } from '@react-spring/web'

const AnimatedDialog = animated(ExternalComponent)

const App = () => {
  const styles = useSpring({
    from: {
      opacity: 0,
      y: '6%',
    },
    to: {
      opacity: 1,
      y: 0,
    },
  })

  return <AnimatedDialog style={styles} />
}
```

If the component you are wrapping does not pass this element then <strong>nothing will animate</strong>.

## Example: using stitches & radix-ui

```jsx live=true template=dialog
import { useState } from 'react'
import { useTransition, animated } from '@react-spring/web'
import { styled } from '@stitches/react'
import * as Dialog from '@radix-ui/react-dialog'

export default function () {
  const [isOpen, setIsOpen] = useState(false)

  const handleDialogChange = (isOpen: boolean) => setIsOpen(isOpen)

  const transition = useTransition(isOpen, {
    from: {
      scale: 0,
      opacity: 0,
    },
    enter: {
      scale: 1,
      opacity: 1,
    },
    leave: {
      scale: 0,
      opacity: 0,
    },
  })

  return (
    <Dialog.Root open={isOpen} onOpenChange={handleDialogChange}>
      <Trigger>
        <TriggerShadow />
        <TriggerEdge />
        <TriggerLabel>Open Modal</TriggerLabel>
      </Trigger>
      <Dialog.Portal forceMount>
        {transition((style, isOpen) => (
          <>
            {isOpen ? (
              <OverlayBackground style={{ opacity: style.opacity }} />
            ) : null}
            {isOpen ? (
              <Content forceMount style={style}>
                <DialogHeader>
                  <CloseButton>
                    <svg
                      width="32"
                      height="32"
                      viewBox="0 0 32 32"
                      fill="none"
                      xmlns="http://www.w3.org/2000/svg">
                      <path
                        d="M15.9574 14.1689L8.59651 6.75098L6.73232 8.59598L14.1313 16.071L6.71338 23.4129L8.5964 25.2769L15.9574 17.8779L23.3943 25.2769L25.2392 23.4129L17.8213 16.071L25.2202 8.59598L23.3752 6.75098L15.9574 14.1689Z"
                        fill="currentColor"
                      />
                    </svg>
                  </CloseButton>
                </DialogHeader>
                <Title>Aha you found me!</Title>
              </Content>
            ) : null}
          </>
        ))}
      </Dialog.Portal>
    </Dialog.Root>
  )
}

const TriggerPart = styled('span', {
  position: 'absolute',
  top: 0,
  left: 0,
  width: '100%',
  height: '100%',
  borderRadius: 8,
})

const TriggerShadow = styled(TriggerPart, {
  background: 'hsl(0deg 0% 0% / 0.1)',
  transform: 'translateY(2px)',
  transition: 'transform 250ms ease-out',
})

const TriggerEdge = styled(TriggerPart, {
  background: `linear-gradient(
      to left,
      hsl(0deg 0% 69%) 0%,
      hsl(0deg 0% 85%) 8%,
      hsl(0deg 0% 85%) 92%,
      hsl(0deg 0% 69%) 100%
    )`,
})

const TriggerLabel = styled('span', {
  display: 'block',
  position: 'relative',
  borderRadius: 8,
  color: '#569AFF',
  fontSize: '14px',
  padding: '16px 24px',
  background: '#fafafa',
  transform: 'translateY(-4px)',
  width: '100%',
  userSelect: 'none',
  transition: 'transform 250ms ease-out',
})

const Trigger = styled(Dialog.Trigger, {
  border: 'none',
  fontWeight: 600,
  cursor: 'pointer',
  background: 'transparent',
  position: 'relative',
  padding: 0,
  cursor: 'pointer',
  transition: 'filter 250ms ease-out',

  '&:hover': {
    filter: 'brightness(110%)',

    [`& ${TriggerLabel}`]: {
      transform: 'translateY(-6px)',
    },

    [`& ${TriggerShadow}`]: {
      transform: 'translateY(4px)',
    },
  },

  '&:active': {
    [`& ${TriggerLabel}`]: {
      transform: 'translateY(-2px)',
      transition: 'transform 34ms',
    },

    [`& ${TriggerShadow}`]: {
      transform: 'translateY(1px)',
      transition: 'transform 34ms',
    },
  },
})

const OverlayBackground = styled(animated(Dialog.Overlay), {
  width: '100vw',
  height: '100vh',
  backgroundColor: 'rgba(0, 0, 0, 0.5)',
  pointerEvents: 'all',
  position: 'fixed',
  inset: 0,
})

const Content = styled(animated(Dialog.Content), {
  position: 'absolute',
  width: '50vw',
  height: '60vh',
  backgroundColor: '#fafafa',
  borderRadius: 8,
  padding: '24px 24px 32px',
})

const DialogHeader = styled('header', {
  display: 'flex',
  justifyContent: 'flex-end',
  marginBottom: 16,
})

const CloseButton = styled(Dialog.Close, {
  backgroundColor: 'transparent',
  border: 'none',
  position: 'absolute',
  top: 16,
  right: 16,
  cursor: 'pointer',
  color: '#1B1A22',
})

const Title = styled(Dialog.Title, {
  fontSize: 20,
})
```

## Deep dive

### How does it work?

Higher-order components have access to the props you're passing to the child. Therefore,
the `animated` component can scan through the passed `style` prop and handle said values.
How the component handles the style prop is dependent on the target you're using. 
Let's concentrate on the `web` target for the sake of explanation.

We start by extracting the whole `style` prop and wrapping it in an `AnimatedStyle` class; this
class is unique to the `web` target and handles the shorthands for transformations and in theory
could handle a multitude of interpolations if we were to add them. Once we performed the specific
target alterations to the style prop we call the `super` of its class `AnimatedObject`, which then runs
through the object making the values "animated".

### How does it update the native element without causing a react render?

Similar to how the `style` prop is handled in a target-specific manner, how we apply
the animated values to the native element is also specific to the target. In the instance of the `web` target,
during the process of wrapping the element in the `animated` HOC we attach a `ref` to the element.

We are then able to directly change the DOM node imperatively. For further reading, check out [`Controller`](/docs/concepts/controllers-and-springs#Controller).

### How can I make my own animated components?

Because animated components belong to the target, to create your own registry of animated components
e.g. for a [D3](https://d3js.org/) react renderer, you would need to use the `createHost` function
exported from the `@react-spring/animated` package. Its signature looks like this:

```ts
interface HostConfig {
  /** Provide custom logic for native updates */
  applyAnimatedValues: (node: any, props: Lookup) => boolean | void
  /** Wrap the `style` prop with an animated node */
  createAnimatedStyle: (style: Lookup) => Animated
  /** Intercept props before they're passed to an animated component */
  getComponentProps: (props: Lookup) => typeof props
}

type AnimatableComponent = string | Exclude<React.ElementType<any>, string>

type CreateHost = (
  components: AnimatableComponent[] | { [key: string]: AnimatableComponent },
  config: Partial<HostConfig>
) => {
  animated: WithAnimated
}
```

See [targets](/docs/concepts/targets) for more information.
