如何实现shadcn全主题切换功能?仿GitHub主题选择交互
实现shadcn主题选择卡片的交互功能
1. 先明确shadcn主题的切换逻辑
shadcn的主题依赖Tailwind CSS的自定义配置,通常是通过给根元素(html)设置data-theme属性,或者替换全局CSS变量来实现主题切换。先确认你的项目中主题是绑定到data-theme还是直接用类名,大部分场景用data-theme更通用。
2. 核心交互实现步骤
(1)状态管理与初始化
不管用框架(React/Vue)还是原生JS,都需要跟踪当前选中的主题,并从本地存储读取用户之前的选择(如果有),默认可跟随系统主题偏好。
React示例:
import { useState, useEffect } from 'react'; export function ThemeSelector() { // 从localStorage读取历史主题,默认用系统偏好或light const [activeTheme, setActiveTheme] = useState(() => { const saved = localStorage.getItem('shadcn-theme'); return saved || window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; }); // 切换主题时更新根元素属性与本地存储 useEffect(() => { document.documentElement.setAttribute('data-theme', activeTheme); localStorage.setItem('shadcn-theme', activeTheme); }, [activeTheme]); // 定义所有shadcn主题列表 const shadcnThemes = [ { id: 'light', name: 'Light', previewColor: '#f9fafb' }, { id: 'dark', name: 'Dark', previewColor: '#111827' }, { id: 'slate', name: 'Slate', previewColor: '#1e293b' }, { id: 'gray', name: 'Gray', previewColor: '#374151' }, { id: 'zinc', name: 'Zinc', previewColor: '#27272a' }, { id: 'neutral', name: 'Neutral', previewColor: '#2a2a2a' }, { id: 'stone', name: 'Stone', previewColor: '#2c2c2c' }, ]; return ( <div className="grid grid-cols-4 gap-3 p-4"> {shadcnThemes.map(theme => ( <div key={theme.id} className={`theme-card rounded-lg p-3 cursor-pointer transition-all border-2 ${ activeTheme === theme.id ? 'border-blue-500' : 'border-transparent' }`} onClick={() => setActiveTheme(theme.id)} style={{ backgroundColor: theme.previewColor }} > <span className={`text-sm font-medium ${activeTheme === theme.id ? 'text-white' : 'text-gray-800'}`}> {theme.name} </span> {activeTheme === theme.id && ( <span className="inline-block ml-2 bg-white text-gray-900 rounded-full px-2 text-xs">✓</span> )} </div> ))} </div> ); }
原生JS示例:
<div class="theme-selector grid grid-cols-4 gap-3 p-4" id="themeSelector"> <!-- 主题卡片 --> <div class="theme-card rounded-lg p-3 cursor-pointer border-transparent border-2" data-theme="light" style="background-color: #f9fafb"> <span class="text-sm font-medium text-gray-800">Light</span> </div> <div class="theme-card rounded-lg p-3 cursor-pointer border-transparent border-2" data-theme="dark" style="background-color: #111827"> <span class="text-sm font-medium text-white">Dark</span> </div> <!-- 其他shadcn主题卡片重复上述结构 --> </div> <script> // 初始化主题 const savedTheme = localStorage.getItem('shadcn-theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); document.documentElement.setAttribute('data-theme', savedTheme); // 标记激活卡片 document.querySelector(`[data-theme="${savedTheme}"]`).classList.add('border-blue-500'); document.querySelector(`[data-theme="${savedTheme}"]`).innerHTML += '<span class="inline-block ml-2 bg-white text-gray-900 rounded-full px-2 text-xs">✓</span>'; // 绑定点击事件 document.querySelectorAll('.theme-card').forEach(card => { card.addEventListener('click', () => { const targetTheme = card.getAttribute('data-theme'); // 更新根元素主题属性 document.documentElement.setAttribute('data-theme', targetTheme); // 保存到本地存储 localStorage.setItem('shadcn-theme', targetTheme); // 更新所有卡片的激活状态 document.querySelectorAll('.theme-card').forEach(c => { c.classList.remove('border-blue-500'); const checkmark = c.querySelector('span:last-child'); if (checkmark) checkmark.remove(); }); card.classList.add('border-blue-500'); card.innerHTML += '<span class="inline-block ml-2 bg-white text-gray-900 rounded-full px-2 text-xs">✓</span>'; }); }); </script>
(2)激活状态视觉反馈
给选中的主题卡片添加明显样式,比如蓝色边框、勾选标记,让用户直观感知当前选中项:
.theme-card:hover { transform: translateY(-2px); } .theme-card.active { border-color: #3b82f6; }
(3)系统主题同步(可选)
如果需要跟随系统主题变化,添加监听逻辑:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { const systemTheme = e.matches ? 'dark' : 'light'; // 仅当用户未手动设置过主题时,跟随系统 if (!localStorage.getItem('shadcn-theme')) { document.documentElement.setAttribute('data-theme', systemTheme); // 更新卡片激活状态(复用之前的逻辑) } });
内容的提问来源于stack exchange,提问作者alieldeba
相关产品推荐
相关产品推荐

