You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React中使用Tailwind CSS实现带动画的标签切换效果?

Fix Animated Tab Switch Without Shifting Button Text in React + Tailwind CSS

The issue with your current implementation is that you're applying the sliding transform directly to the button container—this makes the entire container (including the text) move. To fix this, we need to separate the animated background slider from the interactive buttons. The slider will sit behind the buttons, moving independently, while the buttons stay in place to handle clicks.

Here's the revised code that achieves the desired effect:

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

export const Modal = () => {
  const [theme, setTheme] = React.useState<'light' | 'dark' | 'system'>('light')

  // Calculate slider position based on active theme
  const sliderPosition = clsx({
    'left-2': theme === 'light',
    'left-[calc(33.333%+2px)]': theme === 'dark',
    'left-[calc(66.666%+2px)]': theme === 'system',
  })

  return (
    <div className="relative flex mx-2 mt-2 rounded-md bg-blue-gray-100">
      {/* Animated background slider - sits behind buttons */}
      <div 
        className={clsx(
          'absolute top-2 bottom-2 rounded-md bg-white transition-all duration-300 ease-out',
          sliderPosition,
          'w-[calc(33.333%-4px)]' // Account for left/right margins
        )}
      />

      {/* Light Theme Button */}
      <div className="flex-1 py-1 my-2 ml-2 text-center rounded-md">
        <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>

      {/* Dark Theme Button */}
      <div className="flex-1 py-1 my-2 ml-2 text-center rounded-md">
        <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>

      {/* System Theme Button */}
      <div className="flex-1 py-1 my-2 mr-2 text-center rounded-md">
        <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>
  )
}

Key Changes Explained:

  1. Relative Parent Container: Added relative to the outer <div> so the absolute-positioned slider can be positioned relative to it.
  2. Independent Slider Element: Created a separate slider <div> that sits behind the buttons. It uses absolute positioning, so it doesn't affect the layout of the buttons.
  3. Dynamic Slider Position: Calculated the slider's left position using percentage values (33.333% per tab) to account for the flex-1 widths. The calc() adjusts for the ml-2 margins on the buttons.
  4. Cleaned Up Button Containers: Removed the bg-white and transform styles from the button containers—now the slider provides the selected background effect, while buttons only handle text styling and clicks.
  5. Smooth Transition: Used transition-all duration-300 ease-out on the slider for a smooth animated shift between tabs.

This setup keeps your buttons as the interactive elements while letting the background slider move independently, so your text stays in place during the animation.

内容的提问来源于stack exchange,提问作者deadcoder0904

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 07:52:41