项目不算复杂,文章不想看的源码可自取
github地址:shadcn-tailwind-theme-switcher
介绍
从0开始开发一个基于shadcn和tailwindcss v4.1的主题切换功能。
查看效果:页面预览
如何刚好是你想要的再读不迟 ~
几个作用。现在很多项目都有主题切换功能,但搞的太复杂理不清其中的逻辑;另外,大家混用shadcn/tailwind常常搞不清二者的功能边界;最后tailwind从3到4也有很多变化。借此梳理一下,让内心稍微平静一点。
创建项目
使用vite工具快速创建一个react项目,以该项目作为基础逐步丰富代码
个人感觉pnpm四个字母打字时有点别扭,就配成p了,下文所有的p指定代表pnpm
p create vite .
在当前目录使用vite创建项目,后面选择react
typescript
p install
安装依赖p dev
启动服务- 点击控制台url打开页面,上面这几步无任何障碍,搭建react项目首选vite很丝滑。
引入tailwindCSS
p install tailwindcss @tailwindcss/vite
- 添加插件给vite
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({plugins: [tailwindcss(),],
})
- 创建 src/global.css,导入tailwind默认能力。(v4之前要分别导入,现在一句顶三句)
@import "tailwindcss";
- 在App.tsx中引入该 gloabl.css,后面就可以愉快的使用tailwind了
function App() {return (<><h1 className="text-3xl font-bold text-red-500">Hello world!</h1></>)
}
- 更多的tailwind知识如变量、样式覆盖等可以看看现成的文章 这篇
增加深/浅模式切换功能
tailwindcss v4 中采用了 CSS-first 配置方式,可以直接在 CSS 文件中配置所有内容,理论上不需要有tailwind.config.ts文件了,但依旧保持对该文件的支持(向下兼容)。鉴于现在大部分资料都基于tailwind.config.ts,彻底删除可能会带来不小的额外成本(需要查css中如何对齐config的能力)。
添加tailwind darkMode
- 创建tailwind.config.ts文件,增加
darkMode: 'class'
export default {darkMode: 'class',content: ["./index.html","./src/**/*.{js,ts,jsx,tsx}",],theme: {extend: {colors: {green: {100: '#FF0000',},red: {100: '#00FF00',},},},},plugins: [],
}
- 设置完上述操作后,如果页面中使用了bg: 开头的样式,那当设置
<html class="dark"></html>
时该样式便会生效。
介绍shadcn/ui
- shadcn/ui 跟常规ui库最大的差异是shadcn/ui会把你需要的组件代码直接copy到你的项目中,而不是增加npm包,所以他的导入方式不是pnpm add shadcn/ui。
- 但使用后会发现,package.json中增加了很多
@radix-ui/
开头多包,radix-ui是一个只提供基础能力但不提供样式的ui库,即无头headless样式库,shadcn是在它的基础上使用tailwind语法实现了自己的组件样式。 - 要不要纠结radix-ui的引入破坏了shadcn声称的把代码权限完全交给开发者,而不是给一个npm包理念。个人觉得99%的场景下不用纠结,因为radix-ui基于WAI-ARIA 设计模式只实现了最基础的能力,把样式的自定义完全留给开发者。
安装shadcn/ui
- 在 tsconfig.json 和 tsconfig.app.json 中增加下面配置
{ "compilerOptions": { // ... "baseUrl": ".", "paths": { "@/*": [ "./src/*" ] } // ... }}
}
- 在vite.config.ts 中增加
resolve: { alias: { "@": path.resolve(__dirname, "./src"), },
}
-
p dlx shadcn@latest init
初始化shadcn,p dlx 等价于 npx- 初始化后会创建 components.json
- 还会自动找到tailwind到样式文件,global.css,然后插入一堆默认的样式变量,如下:
-
在模式切换中tailwind和shadcn的作用:
- tailwind可以自动根据根目录的dark类,切换bg: 开头的变量。自己在写样式时如果需要给暗黑模式下一个单独的样式,就需要写很多bg:xxx
- shadcn库里的组件,本身就实现了对不同主题下的风格可变性,就是通过css变量实现的。简单讲,shadcn所有组件的颜色都是走全局变量的(不会写死),当切主题时只要改变这些变量即可。当然这些变量都已经暴露出来了很方便修改。
增加模式切换逻辑
- 增加一个mode-toggle.tsx
import { Moon, Sun } from "lucide-react"
import { Button } from "@/shadcn/components/button"
import {DropdownMenu,DropdownMenuContent,DropdownMenuItem,DropdownMenuTrigger,
} from "@/shadcn/components/dropdown-menu"
import { useTheme } from "@/theme/theme-provider"
export function ModeToggle() {const { setTheme } = useTheme()return (<DropdownMenu><DropdownMenuTrigger asChild><Button variant="outline" size="icon"><Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /><Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /><span className="sr-only">切换主题</span></Button></DropdownMenuTrigger><DropdownMenuContent align="end"><DropdownMenuItem onClick={() => setTheme("light")}>明亮</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme("dark")}>暗黑</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme("system")}>系统</DropdownMenuItem></DropdownMenuContent></DropdownMenu>)
}
- 增加 theme-provider.tsx 添加具体的切换逻辑
- 然后在主文件,main.tsx 和 App.tsx 调用上述代码即可
增加颜色主题模式功能
- 色系变化的核心逻辑:
- global.css 先看下该文件下的配置,重点是其中的
:root[data-theme="rose"]
.dark[data-theme="rose"]
:root[data-theme="green"]
.dark[data-theme="green"]
这些作用域下各自定义了响应色系下的变量,当切换色系时只需动态改变<html data-theme="rose"></html>
里面的颜色属性即可
- global.css 先看下该文件下的配置,重点是其中的
- 从shadcn/ui themes 这里直接copy出自己想要的色系对应的变量,放到global.css里。
- 新增 theme-toggle.tsx 文件,处理色系的选择事件。
- 再在 theme-provider.tsx 添加色系主题相关的变动逻辑即可。
参考:
- 听说你还不会 Tailwind CSS(进阶·下篇) 系列文章含六篇,读完可快速了解tailwind大致能力,缺点是在目前tailwindcss v4版本下稍显过时,还好问题不大。
- Tailwind CSS 文档-中文 还是官网文档来的给力
- Shadcn UI 中文文档 推荐直接看官网,社区文章没看到太好的。