How can I create dynamic tabs using Tailwind CSS?

I am looking to create an animated tab using React and Tailwind CSS. Here is the code I have so far:

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>
  )
}

However, the current implementation causes the text to move when the theme is not 'light' due to the use of translate-x-10.

I want the UI to remain consistent while still having animated tabs like in the example above. Any suggestions on how to achieve this?

You can view a minimal Codesandbox demo here → https://codesandbox.io/s/mobx-theme-change-n1nvg?file=/src/App.tsx

Any guidance would be appreciated!

Answer №1

To create a smooth animation, simply insert an additional tag element within the parent container that houses the three buttons.

This added element will keep track of which button is currently active and adjust its position accordingly based on the button's width.

For instance, if the first button is selected, the element remains stationary at position 0 as it aligns with the first button.

The element responsible for animating should be set to absolute positioning:

.tab-item-animate {
  position: absolute;
  top: 6px;
  left: 6px;
  width: calc(100% - 12px);
  height: 32px;
  transform-origin: 0 0;
  transition: transform 0.25s;
}

Active state for the first button:

.tabs .tabs-item:first-child.active ~ .tab-item-animate {
  transform: translateX(0) scaleX(0.333);
}

Active state for the second button:

.tabs .tabs-item:nth-child(2).active ~ .tab-item-animate {
  transform: translateX(33.333%) scaleX(0.333);
}

Active state for the third button:

.tabs .tabs-item:nth-child(3).active ~ .tab-item-animate {
  transform: translateX(66.666%) scaleX(0.333);
}

While I'm not highly familiar with Tailwind CSS, you may potentially achieve similar results by tweaking my code or exploring other methods within Tailwind itself.

I've included a separate CSS file for this purpose and created a demo using your provided code:

View the animated tabs here

PS: I made some adjustments to your HTML structure; adding an extra div above each button is unnecessary in this scenario.

Answer №2

It turns out that achieving this is possible using solely Tailwind CSS.

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

I'm unsure why it's not animating on Codesandbox, but it functions correctly locally. Potentially a Codesandbox glitch :)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tic Tac Toe is not functioning properly

Hey there! Can someone please help me figure out what's going on? I'm trying to create a Tic Tac Toe game, but instead of using "O" and "X" to mark the fields, I want to use different colors. Specifically, one player should be able to mark with b ...

Creating a tab resizing effect similar to Chrome using only CSS

I am attempting to design tabs that can dynamically resize similar to how Chrome tabs behave. However, I am uncertain if achieving this functionality is possible without relying on JavaScript. Browser compatibility is not a concern for me; my main goal is ...

Positioning a list item on the left side within Chrome by using the

It seems that Chrome is displaying the content incorrectly when I use float: left in a block inside an li element. Check out this JSFiddle link <body> <ol> <li> <div class="input"></div> <div class="te ...

"Encountered a TypeError: Cannot read property 'params

I've encountered an issue with passing the id to my product page. Despite trying various solutions and searching for answers, I still can't get it to work. Below is my index.js code: import React from "react"; import {render} from &quo ...

Modify the arrow design for the expansion panel's default arrow style

Looking to customize the design of an angular expansion panel? Check out the images below for inspiration: Before Customization: https://i.sstatic.net/4u6NS.png After Customization (Not expanded): https://i.sstatic.net/8N6Br.png After Customization (E ...

Issue with styling Select component using sx prop in Material ui version 5

I am trying to customize a dropdown on a grey background with white text. The issue I am facing is that the arrow next to the text remains black instead of changing to white as well. I have checked the documentation but could not find any information on wh ...

Why is it that my terminal doesn't close after my gulp process completes?

I am looking to implement React in my NodeJs application. Here is the content of my gulpfile: let gulp = require('gulp'); let uglify = require('gulp-uglify'); let browserify = require('browserify'); let babelify = require(& ...

Allowing Users to Easily Copy CSS ::before Content

Text inserted via pseudo-elements like ::before and ::after cannot be selected or copied. Is there a way to change this behavior? span::before { content: "including this text"; } <p> When the text of this paragraph is selected and copied, ...

WordPress presents a challenge with PHP Heredoc not displaying variables in HTML

I've coded some functions in my theme's functions.php file. Here is an example: $usrProfileHTML = <<<EOD <div class="eUserProfile"> <div class="eUsrImage"> <img src="{$envUserProfile['eUsrImage']}" a ...

Padding on a flex item nudges a neighboring flex item out of place

Encountering an issue - within a div container, I have 4 nested divs using the grid system. Oddly enough, the third div in the grid is not behaving as expected. When setting a margin-bottom for this div, instead of creating space at the bottom, it pushes ...

What is the method to trigger a function upon opening an anchor tag?

When a user opens a link in my React app, I need to send a post request with a payload to my server. My current strategy involves using the onClick and onAuxClick callbacks to handle the link click event. However, I have to filter out right-clicks because ...

Issue with date: date.toLocaleTimeString function is invalid

I'm currently working on creating a time display component using hooks, but I'm running into an issue with the error message "TypeError: date.toLocaleTimeString is not a function." As a newcomer to this technology, any guidance would be greatly a ...

Upon clicking a button, I aim to retrieve the data from the rows that the user has checked. I am currently unsure of how to iterate through the rows in my mat-table

My goal is to iterate through the column of my mat-table to identify the rows that have been checked, and then store the data of those rows in an array. <td mat-cell *matCellDef="let row"> <mat-checkbox (click)="$event.stopPropagation()" (c ...

What is the functionality of the basePath feature in Next.js when it comes to managing

I have encountered an issue while trying to incorporate a basePath into my next.config.js. Although it works perfectly for the website's content, all the images appear broken. // next.config.js module.exports = { basePath: '/my-sub-url', ...

Looking to enhance your navigation menu with drop shadows?

I am trying to add a drop shadow effect to the navigation menu on my WordPress website. You can view the desired effect here. However, I have attempted to apply it to my .navigation-main class without success. Below is the header code: <header id="mast ...

Trigger a drop-down list in React when typing a specific character, like {{ or @, in an input field

Is there a way in React.js to display a list or dropdown when a user types a certain character like '@' or '{{ }}' in the input text area? The user should be able to select an option from this list and have it inserted into the text are ...

Making sure that a footer div is consistently positioned at the bottom of the container div, regardless of the dimensions of the other elements

I am facing a challenge with a container div which has child elements. <div class='wrapper'> <div class='content'></div> <div class='footer'></div> </div> The height o ...

Creating a Material-UI Menu with Hover instead of Click

Hi there! I'm currently working with the Material-UI Menu component and trying to make it open on mouse hover rather than click. You can check out my code here. Although the menu opens correctly with mouse hover, it doesn't close when the mouse ...

How can I achieve a stylish scrolling header similar to a Google Blog for my website?

Google's blog features a unique header design - as you scroll down, the gray bar in the header moves up until it locks at the top of the screen. While CSS's position:fixed can achieve a similar effect, Google seems to have used a combination of p ...

The functionality for the "OnChange" event in the <html:select> field does not seem to be functioning properly

My JavaScript function isn't functioning properly. I can't see any alert messages on my webpage. Can someone please assist me? Below is my HTML code function checkProjectStatus() { alert("Helloo"); var dropdownType = document.getElementById( ...