DEV Community

Cover image for Framer Motion tutorial: How to easily create React animations
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Framer Motion tutorial: How to easily create React animations

Written by Paul Akinyemi✏️

Animation in React, and on the web at large, is the process of changing the visual state of the UI elements on a page over time. What do I mean by visual state? Any property of the element that influences how it looks: height, shape, position relative to other elements, etc. The core idea of animation is that you’re changing some visible property of something on the page over time.

There are a few ways to create animations in React, but all of them fall into two broad categories: CSS animations, which change visual state by applying CSS rules; and, JavaScript animations, which use JavaScript to change the properties of the element. In either of those categories, you can either implement the animation from scratch, or you can use a library. On the CSS side, you can compose your animations with CSS rules, or you can use third-party libraries like Animate.css.

If you choose to use JavaScript, you can either write custom code to create animations, or you can use libraries like GSAP or Framer Motion. Each library has its advantages, and each has a different approach for write animations. In this article, we’ll explore Framer Motion, a React animation library created and maintained by the Framer design team.

You’ll learn the core components that underpin all Framer Motion animations, dive into some of the features that make Framer Motion a great tool, discover best practices to get the most out of the library, and put it all into practice with a step-by-step example.

Jump ahead:

To follow along, you’ll need:

  • Working knowledge of React, vanilla HTML, CSS, and JS
  • Working knowledge of the command line and npm

Why Framer Motion for React animations?

Why should you consider using Framer Motion in your React project? Framer Motion is a fairly popular and actively maintained library, with 19k stars on GitHub, and plenty of resources to support it.

But most importantly, Framer Motion is built around the idea of allowing you to write complex, production-grade animations with as little code as possible. Using Framer Motion is so convenient that you can implement drag-and-drop by adding a single line of code! Framer Motion also greatly simplifies tasks like SVG animation and animating layout shifts.

Framer Motion components and APIs

Framer Motion has an intuitive approach to animation. It provides a set of components that wrap your markup and accept props to allow you to specify what type of animation you want. The core components of Framer Motion are:

  • The motion component
  • The AnimatePresence component

The motion component provides the foundation of all animation. It wraps the HTML elements in your React components and animates those elements with state passed to its initial and animate props. Below is an example. Take a plain div you might find anywhere on the web:

<div>I have some content here</div>
Enter fullscreen mode Exit fullscreen mode

Let’s assume you wanted this div to fade into the page when it loads. This code is all you need:

<motion.div
  initial={{ opacity:0 }}
  animate={{ opacity:1 }}
>
  I have some content in here 
</motion.div>
Enter fullscreen mode Exit fullscreen mode

When the page loads, the div will animate smoothly from transparency to full opacity, gradually fading into the page. In general, when the motion component is mounted, the values specified in the initial prop are applied to the component, and then the component is animated until it reaches the values specified in the animate prop.

Next, let’s look at AnimatePresence. This component works with motion and is necessary to allow elements you remove from the DOM to show exit animations before they’re removed from the page. AnimatePresence only works on its direct children that fulfill one of two conditions:

  • The child is wrapped with a motion component
  • The child has an element wrapped with a motion component as one of its children

The exit animation you want has to be specified by adding the exit prop to motion. Here’s an example of AnimatePresence at work:

<AnimatePresence>
  <motion.div
    exit={{ x: "-100vh", opacity: 0 }}
  >
    Watch me go woosh!
  </motion.div>
</AnimatePresence>
Enter fullscreen mode Exit fullscreen mode

When the div wrapped by AnimatePresence is removed from the DOM, instead of just disappearing, it will slide 100vh to the left, fading into transparency as it does so. Only after that will the div be removed from the page. Note that when multiple components are direct children of AnimatePresence, they each need to have a key prop with a unique value so AnimatePresence can keep track of them in the DOM.

Those two components are all you need for many animations, but Framer Motion has features that allow for more complex uses. One of those features is a set of props to a motion component that allows the component to trigger animations in response to gestures made by the end user, like hovering, tapping, or dragging page elements. These props are referred to as gestures. Here's a quick example that shows the use of the hover gesture:

<motion.div
  whileHover={{
    opacity: 0
  }}
>
  Hover over me and I'll disappear!
</motion.div>
Enter fullscreen mode Exit fullscreen mode

The whileHover prop is the hover gesture. The code above will fade out the div while it’s being hovered over, and return it to its previous state when the mouse leaves it.

Let’s look at one last feature before we try a bigger example. What do you use if you want to tweak aspects of the animation, like adjusting the duration or delay? Framer Motion provides a transition prop that allows you to specify those. Framer Motion also allows you to choose between different types of animation, like spring animations and tween (easing-based) animations, and the transition prop allows you to control that. Here’s an example:

<motion.div
  initial={{ opacity:0 }}
  animate={{ opacity:1 }}
  transition={{ duration: 0.5, delay: 0.1 }}
>
  I have some content here 
</motion.div>
Enter fullscreen mode Exit fullscreen mode

This is the same fade-in animation from earlier, but because of the transition prop, the animation will wait 0.1 seconds to start and will last for 0.5 seconds.

Implementing animations in a React app using Framer Motion

Let’s use everything we’ve learned to put together a more complex example. At the end of this article, you’ll have built an animated notification tray that looks like this: The Animated Notification Tray We Built In Framer Motion

Project setup and starter code

Start by navigating to the directory where you want the example to live. Next, open your terminal and create a starter React app using Vite with this command:

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

Then, answer the prompts like this: React Project Setup With Vite Now, cd into the project you just created, run npm install, and then run npm run dev. Your project folder should now look like this: Framer Project Folder Delete the src/assets folder and App.css. Now, write the code for the navigation tray without any animation. Start with the project’s CSS by replacing the contents of index.css with the following:

:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  font-weight: 400;
}
body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
  background-color: #fff;
  color: #111827;
}
header {
  height: 4rem;
  font-size: 1.1rem;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  align-items: center;
  padding: 0 2rem;
}
header > .header__left {
  width: 50%;
  font-size: 1.5rem;
}
header > .header__right {
  width: 50%;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  list-style-type: none;
  margin: 0;
  padding: 0;
}
.header__right > * {
  margin: 0 1.5rem;
  position: relative;
}
.header__right > .notification__button {
  height: 2rem;
  width: 2rem;
  cursor: pointer;
}
.header__right > .notification__icon {
  height: 100%;
  width: 100%;
}
.header__right > .image {
  border-radius: 50%;
  height: 3rem;
  width: 3rem;
  overflow: hidden;
}
.header__right > .image > img {
  height: 100%;
  width: 100%;
}
.notification__tray {
  border-radius: 6px;
  box-shadow: 0px 0px 8px #e5e7eb;
  position: fixed;
  width: 24rem;
  top: 4.5rem;
  right: 2rem;
  color: rgb(65, 65, 81);
  font-size: 0.875rem;
  line-height: 1.25rem;
}
.notification__tray > ul {
  list-style-type: none;
  margin: 0;
  padding: 0;
}
.notification__tray li {
  padding: 1rem 2rem;
  border-bottom: 1px solid #e5e7eb;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.notification__tray li:hover {
  background-color: #e5e7eb;
  color: #111827;
}
.notification__tray li .clear__button {
  width: 1.5rem;
  height: 1.5rem;
  cursor: pointer;
}
.notification__tray li .clear__icon {
  width: 100%;
  height: 100%;
}
.todo__header {
  text-align: center;
}
.todo__container {
  list-style-type: none;
}
.todo__item {
  border: 1px solid #e5e7eb;
  border-radius: 5px;
  box-shadow: 0px 0px 8px #e5e7eb;
  color: #111827;
  margin: 1.5rem auto;
  width: 350px;
  padding: 1.5rem 2rem;
  background-color: #e5e7eb;
}
Enter fullscreen mode Exit fullscreen mode

Next comes the code for the page header. In src, create a file called Header.jsx, and fill it with this:

import { useState } from "react";
import NotificationTray from "./NotificationTray";

const initialNotifications = [
  "User #20 left you a like!",
  "User #45 sent you a friend request",
  "Your song has been uploaded!",
  "Thanks for signing up!",
];

const Header = () => {
  const [showNotifications, setShowNotifications] = useState(false);
  const [notificationContent, setNotificationContent] =
    useState(initialNotifications);

  const handleDeleteNotification = (content) => {
    setNotificationContent(
      notificationContent.filter((item) => item !== content)
    );
  };

  return (
    <header>
      <div className="header__left">Brand</div>
      <ul className="header__right">
        <li
          className="notification__button"
          onClick={() => {
            setShowNotifications(!showNotifications);
          }}
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth="1.5"
            stroke="currentColor"
            className="notification__icon"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
            />
          </svg>
        </li>
        <li className="image">
          <img src="https://www.dummyimage.com/48x48" />
        </li>
      </ul>

      {showNotifications ? (
        <NotificationTray
          notificationContent={notificationContent}
          handleDeleteNotification={handleDeleteNotification}
        ></NotificationTray>
      ) : null}
    </header>
  );
};
export default Header;
Enter fullscreen mode Exit fullscreen mode

For brevity, we won’t go over the starter code in detail, but essentially it does a few things:

  • Imports the component for the notification tray
  • Creates state for the tray and defines a helper function to remove notifications from the state
  • Creates the markup for the header, and conditionally renders the tray

Next, write the code for the notification tray component. Create a file called NotificationTray.jsx and put this code in it:

const NotificationTray = ({
  notificationContent,
  handleDeleteNotification,
}) => {
  return (
    <div className="notification__tray">
      <ul>
        {notificationContent.map((content) => {
          return (
            <li key={content}>
              <span>{content}</span>
              <span
                className="clear__button"
                onClick={() => {
                  handleDeleteNotification(content);
                }}
              >
                <svg
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  strokeWidth={1.5}
                  stroke="currentColor"
                  className="clear__icon"
                  title="Clear notification"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                  />
                </svg>
              </span>
            </li>
          );
        })}
      </ul>
    </div>
  );
};
export default NotificationTray;
Enter fullscreen mode Exit fullscreen mode

This code:

  • Renders the state of the tray as a <ul>, with <li>s for each notification
  • Uses the helper function from Header.jsx to remove a notification when its clear button is clicked

Finally, render the header in App.jsx like this:

import Header from "./Header"

function App() {
  return (
    <>
      <Header></Header>
    </>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

All of that covers the code necessary to get the notification tray working properly. If you look at your React app in the browser, you should have a webpage that looks like this: Webpage With Working Notification Tray

Adding animation with Framer Motion

We’ll start animating a bell icon. The ringing motion that triggers on hover is created by rotating the SVG icon along the z-axis, in one direction and then the other, and then returning it to normal.

Here’s how: at the top of Header.jsx, import motion and AnimatePresence from Framer Motion:

import {motion, AnimatePresence} from "framer-motion";
Enter fullscreen mode Exit fullscreen mode

Then, add animation to the SVG in Header.jsx:

<motion.svg
  whileHover={{
    rotateZ: [0, -20, 20, -20, 20, -20, 20, 0],
    transition: { duration: 0.5 },
  }}
  xmlns="http://www.w3.org/2000/svg"
  fill="none"
  viewBox="0 0 24 24"
  strokeWidth="1.5"
  stroke="currentColor"
  className="notification__icon"
>
  <path
    strokeLinecap="round"
    strokeLinejoin="round"
    d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
  />
</motion.svg>
Enter fullscreen mode Exit fullscreen mode

Here’s how the SVG changed:

  • It’s wrapped with motion
  • It has the whileHover gesture as a prop, so the animation only triggers when the SVG is hovered over
  • Inside the object passed to whileHover, you specified a range of values in an array. This array is called a keyframe, and it means that instead of animating to a single value, Framer Motion will animate the SVG to each of the values specified in the keyframe
  • Inside the object you passed to transition, you specified that you want the animation to last half a second

After applying that, you should see the bell ring when you hover over it on your webpage: Animated Bell Icon The next animation you’ll implement is the fade-in on entry animation on the notification tray. Enter Notification.jsx and import motion and AnimatePresence:

import { motion, AnimatePresence } from "framer-motion";
Enter fullscreen mode Exit fullscreen mode

Then, modify the outermost div like so:

<motion.div
  className="notification__tray"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
>
  <ul>
    .....
  </ul>
</motion.div>
Enter fullscreen mode Exit fullscreen mode

All that changed is that the div became motion.div, and you set the values of the initial and animate props so the div starts with an opacity of 0 and animates to become fully visible. Repeat this on the <li>s returned from the map and add a duration of 0.2 seconds like so:

{
  notificationContent.map((content) => {
    return (
      <motion.li
        key={content}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ duration: 0.2 }}
      >
        ...
      </motion.li>
    )
  })
}
Enter fullscreen mode Exit fullscreen mode

As an extra touch, let’s animate the exit of each notification. You’ll do this by adding a slide-away animation when an li is removed from the tray. All you need to do now is wrap the <li>s with an AnimatePresence component, and then use the exit prop to specify what should happen when each <li> is removed. Let’s see how that works:

<ul>
  <AnimatePresence>
    {notificationContent.map((content) => {
      return (
        <motion.li
          key={content}
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ x: "-12rem", opacity: 0 }}
          transition={{ duration: 0.2 }}
          layout
        >
          ....
        </motion.li>
      )
    })}
  </AnimatePresence>
</ul>
Enter fullscreen mode Exit fullscreen mode

The exit prop says that when the <li> is removed, it should move 12rem (half the width of the tray) to the left, and then fade away before being unmounted. The layout prop tells Framer Motion to animate any changes in the element’s position caused by layout shifts. This means that when an <li> is removed from the tray, instead of its siblings jumping up to fill the space, they’ll smoothly glide into their new positions instead. Take a minute to check it out for yourself.

Your last task for this section is to animate the exit of the tray (when it disappears after you click the bell). The animation you want to apply is the same as the exit animation on the <li>s: slide left and fade away.

Go back into Header.jsx and wrap the NotificationTray component with AnimatePresence:

<AnimatePresence>
  {showNotifications ? (
    <NotificationTray
      notificationContent={notificationContent}
      handleDeleteNotification={handleDeleteNotification}
    ></NotificationTray>
  ) : null}
</AnimatePresence>
Enter fullscreen mode Exit fullscreen mode

Then, inside NotificationTray.jsx, add an exit prop to the outermost div:

<motion.div
  className="notification__tray"
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  exit={{ opacity: 0, x: "-12rem" }}
>
  <ul>...</ul>
</motion.div>
Enter fullscreen mode Exit fullscreen mode

And that completes the basics of our animations! Your tray should now have animations that look like this: Working Animations In Our Notification Tray Using Framer Motion Next, you’ll see how to use variants to clean up your code and orchestrate child animations.

Variants in Framer Motion

Variants are a mechanism Framer gives you that serve two purposes:

  • They make your code cleaner by allowing you to reduce repetition
  • They allow you to coordinate the animations of children with the animations of their parents, which means you can do things like stagger child animations

So, what exactly do variants look like? They’re just objects that hold the objects you would pass to the props to a motion component. It’s easier to understand when you see them, so here’s an example:

const dummyVariant = {
  initial: { opacity: 0 },
  animate: { opacity: 1 },
}
Enter fullscreen mode Exit fullscreen mode

The dummyVariant object holds the objects you’d have passed to the initial and animate props. The property names don’t have to match the name of the prop they’ll be assigned to, so the variant would have worked the same if it was simply:

const dummyVariant = {
  hide: { opacity: 0 },
  show: { opacity: 1 },
}
Enter fullscreen mode Exit fullscreen mode

To use a variant on a motion component, you have to pass the variant to the variants prop of your motion component. Then you can pass the props of the motion component the property names of the objects inside of your variant instead of having to type out the full object. Here’s an example:

><motion.div variants={dummyVariant} initial="hide" animate="show">
  Watch me!
</motion.div>
Enter fullscreen mode Exit fullscreen mode

Now that you have a general idea of how variants work, let’s go back and use variants to improve our notification tray. Open NotificationTray.jsx and add this code just under the import:

const notificationVariants = {
  initial: { opacity: 0 },
  animate: { opacity: 1, transition: { staggerChildren: 0.15 } },
  exit: {
    x: "-12rem",
    opacity: 0,
    transition: {
      staggerChildren: 0.15,
      staggerDirection: -1,
      when: "afterChildren",
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

This code introduced a few new properties, so let’s examine them. Variants allow you to adjust the timing of animations on child elements relative to the animation on their parents with special transition props like staggerChildren. The staggerChildren prop allows you to delay the animation of each child by a constant difference.

For instance, if staggerChildren is set to 0.15, the first child will be delayed by 0 seconds, the second by 0.15, the third by 0.30, and so on. staggerDirection can either be 1 or -1 and allows you to decide whether the stagger should start from the first child or the last one. The when prop applies to the parent. It defines whether the parent’s animation should start before the children start animating or only start after all the children are done.

In the variant you just defined, you staggered the input and exit animation of each child by 0.15 seconds, and said the stagger on the exit animations should start from the last child. You also said the parent’s exit animation should only start after the exit animations of all its children have ended.

Now that you have a variant defined, the outermost div can be refactored to this:

<motion.div
  className="notification__tray"
  variants={notificationVariants}
  initial="initial"
  animate="animate"
  exit="exit"
>
  <ul>...</ul>
</motion.div>
Enter fullscreen mode Exit fullscreen mode

And the <li>s can be refactored to this:

<motion.li
  key={content}
  variants={notificationVariants}
  exit={{ x: "-12rem", opacity: 0 }}
  transition={{ exit: { duration: 0.1 } }}
  layout
>
  ...
</motion.li>
Enter fullscreen mode Exit fullscreen mode

You might notice that the initial and animate props are no longer on the <li>, but the animation still works. This is due to another feature of variants called propagation. Propagation means when a motion component and its children are using variants, the values of the initial and animate props will flow down from the parent to the children until one of the children defines its own animate prop.

After putting it all together, your webpage should now look like this: Using Variants In Framer Motion Animations And that’s it! You can find the complete code for this example in the Framer Motion documentation.

Framer Motion page transitions

Framer Motion can also integrate with React Router 6, allowing you to animate routing transitions with AnimatePresence as pages in your app are mounted and unmounted. Here's an example CodeSandbox from the official docs.

Building a drag-and-drop UI with Framer Motion

Just like everything else, Framer Motion makes implementing drag-and-drop easy. To make an element draggable, first wrap it with a motion component, and then add the drag prop. That’s all you need. As an example, you go from this:

<div>Drag me around!</div>
Enter fullscreen mode Exit fullscreen mode

To this:

<motion.div drag>Drag me around</motion.div>
Enter fullscreen mode Exit fullscreen mode

And that’s it! Doing that will let you drag the div anywhere, including off the screen. That's why Framer Motion provides extra props like dragConstraint to let you limit the range a component can be dragged to, and dragElastic moderates how elastic the boundary is.

dragConstraint accepts either an object with values for top, left, right, and bottom, or a ref to another DOM object. The value of dragElastic ranges from 0, meaning the boundaries aren’t elastic at all, to 1, where the boundaries are as elastic as possible. Here’s an example:

 <motion.div
  drag
  dragConstraints={{
    top: -50,
    left: -50,
    right: 50,
    bottom: 50,
  }}
  dragElastic={0.3}
>
  Drag me around
</motion.div>
Enter fullscreen mode Exit fullscreen mode

Now, the div can only be dragged in a region 50px in any direction, and the boundaries of the region are slightly elastic.

Integrating SVG animations

Framer also makes animating SVGs a breeze, by allowing you to animate the pathLength, pathSpacing, and pathOffset properties of those SVGs. Here’s an example that uses the same bell icon we used in our header bar. Animate the pathLength of the SVG in Header.js by updating it like this:

<motion.svg
  initial={{ pathLength: 0 }}
  animate={{ pathLength: 1 }}
  whileHover={{
    rotateZ: [0, -20, 20, -20, 20, -20, 20, 0],
    transition: { duration: 0.5 },
  }}
  xmlns="http://www.w3.org/2000/svg"
  fill="none"
  viewBox="0 0 24 24"
  strokeWidth="1.5"
  stroke="currentColor"
  className="notification__icon"
>
  <motion.path
    initial={{ pathLength: 0 }}
    animate={{ pathLength: 1 }}
    transition={{ duration: 2 }}
    strokeLinecap="round"
    strokeLinejoin="round"
    d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
  />
</motion.svg>
Enter fullscreen mode Exit fullscreen mode

On page load, the SVG should animate like this:

Best practices for optimizing Framer Motion

Framer Motion is a great tool, but like any tool, it can be misused. Here are a few rules of thumb to optimize performance when using Framer Motion:

  • Use the layoutGroup component and layoutId prop for shared element animations
  • Leverage hardware acceleration by animating transform and opacity properties whenever possible
  • Where possible, wrap multiple elements within a parent component and animate the parent to reduce the number of animations
  • If you have animations that trigger frequently (e.g., on scroll or mouse movement), consider using debounce or throttle techniques to limit the number of animation updates and improve performance
  • Use Framer Motion’s hooks instead of rewriting the functionality yourself

Following these practices will help you optimize performance and create smoother animations with Framer Motion in React.

Conclusion

Web animation is the process of changing the visual state of UI elements over time. Web animation utilizes one of two approaches: CSS or JavaScript. Framer Motion is a popular, well-supported JavaScript-based animation library for React applications that simplifies the process of implementing complex animations. In this article, we used Framer Motion to build an animated notification tray, animate an SVG, and implement drag-and-drop.

I hope you enjoyed this article. Happy coding!


Get setup with LogRocket's modern React error tracking in minutes:

1.Visit https://logrocket.com/signup/ to get an app ID.
2.Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.

NPM:

$ npm i --save logrocket 

// Code:

import LogRocket from 'logrocket'; 
LogRocket.init('app/id');
Enter fullscreen mode Exit fullscreen mode

Script Tag:

Add to your HTML:

<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
Enter fullscreen mode Exit fullscreen mode

3.(Optional) Install plugins for deeper integrations with your stack:

  • Redux middleware
  • ngrx middleware
  • Vuex plugin

Get started now

Top comments (1)

Collapse
 
trisogene profile image
Daniel Dallimore Mallaby

Amazing resource!
It's good for understanding the "lower level" of framer but i would have pushed more on the "higher level" side by explaining hooks like useAnimate or usePresence.