Background parttern

Tab Animation with framer motion

A simple tab animation with framer motion

motion/reactreactanimation

## 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 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 postcss autoprefixer clsx
pnpm add motion/react tailwindcss postcss autoprefixer clsx
yarn add motion/react tailwindcss postcss autoprefixer clsx
bun add motion/react tailwindcss postcss autoprefixer clsx

## Styling the Tabs

We will use tailwindcss to style the tabs. We will create a new file tailwind.config.js in the root of the project and add the following code.

tailwind.config.js
module.exports = {
  experimental: {
    optimizeUniversalDefaults: true
  },
  content: ['./src/**/*.{js,jsx,ts,tsx,vue,mdx,md}'],
  darkMode: 'class',
  theme: {
    extend: {}
  },
  plugins: []
};

We will also create a new file postcss.config.js in the root of the project and add the following code.

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

We will also update the tailwind.css file in the styles folder with the following code.

styles/tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;

## 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.'
  }
];
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">
        {/* menu items */}
        <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 { tabs } from '~/data';
import Tab from '~/components/animated-tab';

export default function Home() {
  return (
    <div className="mx-auto max-w-2xl">
      <Tab contains={tabs} />
    </div>
  );
}
app/page.tsx
import { tabs } from "~/data";
import Tab from "~/components/animated-tab";
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 tailwindcss. Which can be used in any project to display content in a tabbed format. Our tab component is also responsive and can be used on any device. Resulting in a better user experience. 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

3 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