Reordering Lists in Chakra-UI

About This Tutorial

This tutorial is designed first-and-foremost as a proof of concept for highly interactive tutorials.

As you can see on the right side of the screen, there's an interactive playground that we'll be using to demonstrate code.

Throughout the tutorial you will see buttons like this: package.json

When you click them, they will perform operations on the sandbox on the right side of the screen. The buttons in this paragraph will open the specified file in the editor. For example, the following button will open App.js and this next one will open package.json.

Objective

Chakra UI is an awesome component library and it couples nicely with framer-motion, an awesome animation library.

Today I'm going to show you how to turn a boring static list into a spicy reorderable list while preserving code-readability.

Ok, Let's Do It

To get us started, I've already gone ahead and implemented a basic list of usernames using chakra-ui's style props. We're going to add to this implementation throughout the tutorial to make it more interesting.

App.js

The list is super boring right now so let's go ahead and add an avatar for each user. We've already included a package react-jazzicon in the package.json file, which will allow us to render cool avatars.

Let's make a new file, User.jsx, and in here we'll put a user component.

User.jsx

Even after applying these changes, we won't see any diffence in the ui yet because we're not using the User component anywhere.

So now let's import User in App.js and render it within the ListItem.

App.js

Sweet, now that we've got a better looking list, let's try to make it interactive. To do this we'll user framer-motion's Reorder.Group and Reorder.Item components.

Strictly using framer-motion, we could implement a reorderable list as follows.

However, then we would lose the ability to style our components using chakra-ui.

So instead, we'll use the as prop on our chakra components to render framer-motion's reorder components under the hood.

App.js

Now, if you play around with the rendered list in the sandbox you'll notice that the list can be reordered and will animate smoothly.

By using the as prop we're effectively able to combine the behaviors of two components into one.

We've kept all of our list styling props, but are now also passing reorder props to the list components.

However there's still some details that don't quite feel right. For example, while dragging an element sometimes it will go behind adjacent elements. Another issue is that the animation speed is rather slow and sluggish. Yet another issue is that when we hover over the elements, they don't feel interactive even though they are.

Let's start by addressing the slow animation speed.

We can use the dragTransition prop to control the transition characteristics. Let's see how changing the bounceStiffness affects the feeling of the animation. Try clicking each option below and then reordering the list to see how the animation feels.

    bounceStiffness: 50bounceStiffness: 600bounceStiffness: 2500

You probably noticed that a bounceStiffness of 600 feels most appropriate, so that's what we'll stick with!

Next, let's fix the z-index issue where the element we're actively dragging sometimes appears behind adjacent elements.

To do this we'll use framer-motion's whileDrag prop coupled with framer-motion variants.

App.js

Now when we drag an element we set its z-index to 1 so that it will always be above ajacent elements. Note that we also added postion="relative" to the component. Without this, z-index would not have any affect.

Ok, lastly let's make the list items feel more interactive. To do this we're going to change the cursor when hovering over the list items and we're also going to change the background color and shadow while dragging an element.

App.js

Boom. Now we've got a nice spicy list.

Feel free to fork the sandbox using the button in the bottom right of this page!

import { Center, ChakraProvider, List, ListItem, Text } from "@chakra-ui/react";
import { useState } from "react";

export default function App() {
  const [usernames, setUsernames] = useState([
    "malerba118",
    "compulves",
    "dan_abramov",
  ]);
  return (
    <ChakraProvider>
      <Center h="100vh">
        <List spacing={2}>
          {usernames.map((username) => (
            <ListItem key={username} p={2} bg="gray.100" rounded="lg">
              <Text>{username}</Text>
            </ListItem>
          ))}
        </List>
      </Center>
    </ChakraProvider>
  );
}