Search code examples
htmlcssreactjscss-transitionstailwind-css

Make animated tabs in Tailwind CSS?


I want to make an animated tab like:

animated tabs tailwind

I am using React with Tailwind. This is my code:

import React from 'react'
import clsx from 'clsx'

export const Modal = () => {
  const [theme, setTheme] = React.useState<'light' | 'dark' | 'system'>('light')
  return (
    <div className="flex mx-2 mt-2 rounded-md bg-blue-gray-100">
      <div
        className={clsx('flex-1 py-1 my-2 ml-2 text-center rounded-md', {
          'bg-white': theme === 'light',
          'transition duration-1000 ease-out transform translate-x-10':
            theme !== 'light',
        })}
      >
        <button
          className={clsx(
            'w-full text-sm cursor-pointer select-none focus:outline-none',
            {
              'font-bold text-blue-gray-900': theme === 'light',
              'text-blue-gray-600': theme !== 'light',
            }
          )}
          onClick={() => {
            setTheme('light')
          }}
        >
          Light
        </button>
      </div>
      <div
        className={clsx('flex-1 py-1 my-2 ml-2 text-center rounded-md', {
          'bg-white': theme === 'dark',
        })}
      >
        <button
          className={clsx(
            'w-full text-sm cursor-pointer select-none focus:outline-none',
            {
              'font-bold text-blue-gray-900': theme === 'dark',
              'text-blue-gray-600': theme !== 'dark',
            }
          )}
          onClick={() => {
            setTheme('dark')
          }}
        >
          Dark
        </button>
      </div>
      <div
        className={clsx('flex-1 py-1 my-2 mr-2 text-center rounded-md', {
          'bg-white': theme === 'system',
        })}
      >
        <button
          className={clsx(
            'w-full text-sm cursor-pointer select-none focus:outline-none',
            {
              'font-bold text-blue-gray-900': theme === 'system',
              'text-blue-gray-600': theme !== 'system',
            }
          )}
          onClick={() => {
            setTheme('system')
          }}
        >
          System
        </button>
      </div>
    </div>
  )
}

But it looks like:

my tailwind animated tabs attempt

As I use translate-x-10 when the theme is not light, therefore the text moves as well.

I would love to make the UI exactly as the above one while still using buttons for the actual tabs.

Minimal Codesandbox → https://codesandbox.io/s/mobx-theme-change-n1nvg?file=/src/App.tsx

How do I do it?


Solution

  • Turns out, it is possible with pure Tailwind.

    tailwind.config.js

    module.exports = {
        theme: {
            extend: {
                translate: {
                    200: '200%',
                },
            },
        },
    }
    

    App.tsx

    import * as React from "react"
    import { observer } from "mobx-react"
    import clsx from "clsx"
    
    import { useStore } from "./context"
    
    const AppTheme = observer(() => {
        const {
            theme: { app },
            updateTheme,
        } = useStore()
    
        return (
            <>
                <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
                    <div className="mt-2">
                        <h4 className="text-xl font-bold text-gray-800">Background</h4>
                    </div>
                </div>
    
                <div className="relative mx-2 mt-2 rounded-md bg-gray-100">
                    <div
                        id="slider"
                        className={clsx(
                            "absolute inset-y-0 w-1/3 h-full px-4 py-1 transition-transform transform",
                            {
                                "translate-x-0": app === "light",
                                "translate-x-full": app === "dark",
                                "translate-x-200": app === "system",
                            },
                        )}
                        style={
                            app === "system"
                                ? {
                                        transform: "translateX(200%)", // if you added `translate-x-200` to `tailwind.config.js` then you can remove the `style` tag completely
                                  }
                                : {}
                        }
                    >
                        <div
                            className={clsx(
                                "w-full h-full bg-white rounded-md",
                                {
                                    active: app === "light",
                                    "bg-gray-600": app === "dark",
                                },
                                {
                                    // needs to be separate object otherwise dark/light & system keys overlap resulting in a visual bug
                                    ["bg-gray-600"]: app === "system",
                                },
                            )}
                        ></div>
                    </div>
                    <div className="relative flex w-full h-full">
                        <button
                            tabIndex={0}
                            className={clsx(
                                "py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
                                {
                                    active: app === "light",
                                    "font-bold text--gray-900": app === "light",
                                    "text--gray-600": app !== "light",
                                },
                            )}
                            onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
                                if (event.key === "Tab")
                                    updateTheme({
                                        app: "light",
                                    })
                            }}
                            onClick={() => {
                                updateTheme({
                                    app: "light",
                                })
                            }}
                        >
                            Light
                        </button>
                        <button
                            tabIndex={0}
                            className={clsx(
                                "py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
                                {
                                    active: app === "dark",
                                    "font-bold text-white": app === "dark",
                                    "text--gray-600": app !== "dark",
                                },
                            )}
                            onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
                                if (event.key === "Tab")
                                    updateTheme({
                                        app: "dark",
                                    })
                            }}
                            onClick={() => {
                                updateTheme({
                                    app: "dark",
                                })
                            }}
                        >
                            Dark
                        </button>
                        <button
                            tabIndex={0}
                            className={clsx(
                                "py-1 my-2 ml-2 w-1/3 text-sm cursor-pointer select-none focus:outline-none",
                                {
                                    active: app === "system",
                                    "font-bold text-white": app === "system",
                                    "text--gray-600": app !== "system",
                                },
                            )}
                            onKeyUp={(event: React.KeyboardEvent<HTMLElement>) => {
                                if (event.key === "Tab")
                                    updateTheme({
                                        app: "system",
                                    })
                            }}
                            onClick={() => {
                                updateTheme({
                                    app: "system",
                                })
                            }}
                        >
                            System
                        </button>
                    </div>
                </div>
            </>
        )
    })
    
    export default observer(function App() {
        return <AppTheme />
    })
    

    Codesandbox → https://codesandbox.io/s/mobx-theme-change-animated-18gc6?file=/src/App.tsx

    Idk why it isn't animating on Codesandbox but it works locally. Maybe a Codesandbox bug :)