Background pattern

Tab Animation with motion/react

A simple tab animation with motion/react and Tailwind CSS v4

motion/reactreactanimationtailwindcss

## Requirements

Tabs are a common UI element that can be used to switch between different views. In this post, we will create a simple tab animation with motion/react.

This post assumes that you have a basic understanding of React. If you are new to React, you can follow the React getting started guide. We will also use tailwindcss v4 for styling and motion/react a popular animation library for React.

## Setup

We will install the required dependencies for the project.

npm install motion/react tailwindcss@next @tailwindcss/postcss postcss autoprefixer clsx

## Styling the Tabs

We will use Tailwind CSS v4 to style the tabs. In v4, configuration is now done using CSS instead of JavaScript config files.

### PostCSS Configuration

Create a postcss.config.js file in the root of your project:

postcss.config.js
module.exports = {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {}
  }
};

### Global CSS Configuration

In Tailwind CSS v4, configuration is now done using CSS instead of JavaScript config files. Create or update your app/globals.css file:

app/globals.css
@import 'tailwindcss';

/* Customize your theme using CSS variables */
@theme {
  --color-orange-300: #fdba74;
  --color-orange-300-dark: #ea580c;
}

This CSS-based configuration approach is much simpler than the old JavaScript config. You can define custom colors, spacing, and other design tokens directly in your CSS using the @theme directive.

## Animating the Tabs

We will use the AnimatePresence component from motion/react to animate the tabs. We will also use the motion component to animate the tab pointer.

## Creating the Tabs

### Data for the components

We will create a simple array that will hold the tab data for the component. The data will contain the tab title and content.

data.ts
export const tabs = [
  {
    title: 'Frontend',
    content: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Harum voluptas ducimus.'
  },
  {
    title: 'Backend',
    content: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.'
  }
];

### Tab component

A new file Tab.tsx will be created in the components folder. This component will take the tab data as props and render the tab title and content.

components/animated-tab.tsx
'use client';

import { useId, useState } from 'react';
import { AnimatePresence, motion } from 'motion/react';
import { cn } from 'lib/utils';

interface TabProps {
  title: string;
  content: string;
}

export default function Tab({ contains, className }: { contains: TabProps[]; className?: string }) {
  const [tab, setTab] = useState(contains[0]);
  const animationId = useId();

  return (
    <div className={cn('my-5', className)}>
      <div className="space-y-2">
        <div className="max-xsm:text-sm flex w-full items-center space-x-4 overflow-x-auto border border-orange-300 dark:border-[0.5px]">
          {contains.map((item, idx) => (
            <button
              key={idx}
              className={cn('relative')}
              onClick={() => setTab(item)}
            >
              <AnimatePresence>
                {tab.title === item.title && (
                  <motion.div
                    layoutId={animationId}
                    className={cn(
                      'absolute inset-0 size-full bg-orange-300/50 backdrop-blur',
                      idx === 0 && 'rounded-l-sm',
                      idx === contains.length - 1 && 'rounded-r-sm'
                    )}
                  />
                )}
              </AnimatePresence>
              <div className={cn('relative z-1 flex items-center space-x-2 px-3 py-1 font-light uppercase')}>
                {/* <item.icon className="h-4 w-auto" /> */}
                <span>{item.title}</span>
              </div>
            </button>
          ))}
        </div>
        <div className="border border-orange-300 px-4 py-5 dark:border-[0.5px]">
          {contains.map(
            (item, idx) =>
              item.content === tab.content && (
                <AnimatePresence key={idx}>
                  <motion.div
                    initial={{ y: 50, opacity: 0 }}
                    animate={{ y: 0, opacity: 1 }}
                    exit={{ y: 50, opacity: 0 }}
                    transition={{
                      type: 'spring',
                      duration: 1,
                      delay: 0.25,
                      stiffness: 260,
                      damping: 20
                    }}
                    className="h-[10vh] w-full overflow-y-auto"
                  >
                    {item.content}
                  </motion.div>
                </AnimatePresence>
              )
          )}
        </div>
      </div>
    </div>
  );
}

### Using the Tab component

We will import the Tab.tsx component into your component or page example app/page.tsx file and pass the tabs data as props.

app/page.tsx
import Tab from '~/components/animated-tab';
import { tabs } from '~/data';

export default function Home() {
  return (
    <div className="mx-auto max-w-2xl">
      <Tab contains={tabs} />
    </div>
  );
}

## Conclusion

We have successfully created a tab component with motion/react and Tailwind CSS v4. The new CSS-based configuration using @import "tailwindcss" and the @theme directive makes setup much simpler than the old JavaScript config approach. Our tab component can be used in any project to display content in a tabbed format, is responsive, and works on any device. Below is the result of the tab component we created.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Harum voluptas ducimus.

Published on April 15, 2023

4 min read

Found an Issue!

Find an issue with this post? Think you could clarify, update or add something? All my posts are available to edit on Github. Any fix, little or small, is appreciated!

Edit on GitHub

Last updated on