Animations

Many Reshaped components come with built-in micro-interactions, ranging from simple Button hover and click transitions to more complex animations like those in DropdownMenu.

One of the main themes of Reshaped is its focus on layout composition and flexibility. We provide you with the building blocks so you can combine them based on your product needs. This approach gives you more control over how you animate component layouts.

The most common way to build such animations today is by using framer-motion. It allows you to wrap Reshaped components with motion elements and animate their composition.

It's very easy to handle animations when dynamically adding and removing content by wrapping Reshaped components with motion.li wrappers. framer-motion takes care of everything required for handling layout animations and component lifecycles.

  • Item 1
"use client";

import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Button, Card, View } from "reshaped";

export default () => {
  const [items, setItems] = React.useState([1]);

  return (
    <View gap={2}>
      <View gap={1} as="ul">
        <AnimatePresence initial={false}>
          {items.map((i) => (
            <motion.li
              key={i}
              layout
              initial={{ height: 0, opacity: 0 }}
              animate={{ height: "auto", opacity: 1 }}
              exit={{ height: 0, opacity: 0 }}
            >
              <Card>Item {i}</Card>
            </motion.li>
          ))}
        </AnimatePresence>
      </View>

      <View direction="row" gap={2}>
        <View.Item grow>
          <Button
            fullWidth
            disabled={items.length === 1}
            onClick={() => {
              setItems((prev) => prev.slice(0, -1));
            }}
          >
            Remove
          </Button>
        </View.Item>
        <View.Item grow>
          <Button
            fullWidth
            onClick={() => {
              setItems((prev) => [...prev, items.length + 1]);
            }}
            disabled={items.length >= 5}
            color="primary"
          >
            Add
          </Button>
        </View.Item>
      </View>
    </View>
  );
}

Some components, like Tabs, are compound, meaning you can create custom layouts for them and animate their state updates. In the following example, we animate the selected tab panel visibility using the AnimatePresence component from framer-motion and motion.div inside the Tab.Panel.

"use client";

import React from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Tabs, View } from "reshaped";
import { ActivityIcon } from "lucide-react";

export default () => {
  return (
    <Tabs>
      <View gap={4}>
        <Tabs.List>
          <Tabs.Item value="1" icon={ActivityIcon}>
            Item 1
          </Tabs.Item>
          <Tabs.Item value="2">Long item 2</Tabs.Item>
          <Tabs.Item value="3">Very long item 3</Tabs.Item>
        </Tabs.List>

        <AnimatePresence mode="wait">
          {["1", "2", "3"].map((i) => (
            <Tabs.Panel value={i} key={i}>
              <motion.div
                key={i}
                initial={{ y: 20, opacity: 0 }}
                animate={{ y: 0, opacity: 1 }}
                transition={{ duration: 0.4 }}
              >
                <View
                  padding={6}
                  textAlign="center"
                  backgroundColor="neutral-faded"
                >
                  <View.Item>Tab {i}</View.Item>
                </View>
              </motion.div>
            </Tabs.Panel>
          ))}
        </AnimatePresence>
      </View>
    </Tabs>
  );
}