Background parttern

Tab Animation with framer motion

A simple tab animation with framer motion

framer-motionreactanimation

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 framer-motion.

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 framer-motion a popular animation library for React.

Setup

We will install the required dependencies for the project.

yarn add framer-motion 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 framer-motion 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.

Tab.tsx

'use client'
 
import { useState } from 'react'
import { cn} from 'lib/utils'
import { AnimatePresence, motion } from 'framer-motion'
 
interface TabProps {
  title: string
  content: string
}
 
export default function Tab({ contains, className }: { contains: TabProps[]; className?: string }) {
  const [tab, setTab] = useState(contains[0])
 
  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="tab-example-pointer"
                    className={cn(
                      'absolute inset-0 bottom-0 h-full w-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>
        {/* content */}
        <div className="border border-orange-300 px-4 py-5 dark:border-[0.5px]">
          {/* <code className="h-[30vh] w-full overflow-y-auto">{tab.content}</code> */}
          {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-[30vh] 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/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 framer-motion 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.