Blox/
Theme Customizer
A real-time theme customization panel with color presets, dark mode toggle, and style controls. Great for apps that offer personalization options.
Color PresetsDark ModeLive PreviewRadius Control
Installation
Terminal
TSXReactTailwind
npx @uiblox/cli add theme-customizerPreview
Theme Customizer
Source Code
theme-customizer.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163"use client"; import { useState } from "react"; import { cn } from "@/lib/utils"; const colorPresets = [ { name: "Purple", primary: "#8B5CF6", secondary: "#A78BFA" }, { name: "Blue", primary: "#3B82F6", secondary: "#60A5FA" }, { name: "Green", primary: "#10B981", secondary: "#34D399" }, { name: "Orange", primary: "#F97316", secondary: "#FB923C" }, { name: "Pink", primary: "#EC4899", secondary: "#F472B6" }, { name: "Cyan", primary: "#06B6D4", secondary: "#22D3EE" }, ]; const radiusOptions = ["none", "sm", "md", "lg", "full"]; export function ThemeCustomizer() { const [isOpen, setIsOpen] = useState(true); const [theme, setTheme] = useState({ mode: "light" as "light" | "dark", primaryColor: colorPresets[0], radius: "md", fontSize: 16, }); return ( <> {/* Toggle Button */} <button onClick={() => setIsOpen(!isOpen)} className="fixed right-4 bottom-4 p-3 bg-purple-600 text-white rounded-full shadow-lg hover:bg-purple-700 transition-colors z-50" > <PaletteIcon /> </button> {/* Panel */} <div className={cn( "fixed right-0 top-0 h-full w-80 bg-white dark:bg-slate-900 border-l border-slate-200 dark:border-slate-800 shadow-xl z-40 transition-transform duration-300", isOpen ? "translate-x-0" : "translate-x-full" )}> <div className="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800"> <h2 className="text-lg font-semibold text-slate-900 dark:text-white">Theme Customizer</h2> <button onClick={() => setIsOpen(false)} className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"> <XIcon /> </button> </div> <div className="p-4 space-y-6 overflow-y-auto h-[calc(100%-4rem)]"> {/* Mode Toggle */} <div> <label className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2 block">Mode</label> <div className="flex gap-2"> <button onClick={() => setTheme({ ...theme, mode: "light" })} className={cn( "flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border transition-colors", theme.mode === "light" ? "bg-purple-50 border-purple-500 text-purple-700" : "border-slate-200 dark:border-slate-700" )} > <SunIcon /> Light </button> <button onClick={() => setTheme({ ...theme, mode: "dark" })} className={cn( "flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-lg border transition-colors", theme.mode === "dark" ? "bg-purple-50 border-purple-500 text-purple-700" : "border-slate-200 dark:border-slate-700" )} > <MoonIcon /> Dark </button> </div> </div> {/* Primary Color */} <div> <label className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2 block">Primary Color</label> <div className="grid grid-cols-6 gap-2"> {colorPresets.map((color) => ( <button key={color.name} onClick={() => setTheme({ ...theme, primaryColor: color })} className={cn( "w-10 h-10 rounded-lg transition-transform hover:scale-110", theme.primaryColor.name === color.name && "ring-2 ring-offset-2 ring-slate-900 dark:ring-white" )} style={{ backgroundColor: color.primary }} title={color.name} /> ))} </div> </div> {/* Border Radius */} <div> <label className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2 block">Border Radius</label> <div className="flex gap-1"> {radiusOptions.map((r) => ( <button key={r} onClick={() => setTheme({ ...theme, radius: r })} className={cn( "flex-1 px-3 py-2 text-sm rounded-lg border transition-colors", theme.radius === r ? "bg-purple-50 border-purple-500 text-purple-700" : "border-slate-200 dark:border-slate-700" )} > {r} </button> ))} </div> </div> {/* Font Size */} <div> <label className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2 block"> Font Size: {theme.fontSize}px </label> <input type="range" min="12" max="20" value={theme.fontSize} onChange={(e) => setTheme({ ...theme, fontSize: parseInt(e.target.value) })} className="w-full accent-purple-600" /> </div> {/* Preview */} <div> <label className="text-sm font-medium text-slate-700 dark:text-slate-300 mb-2 block">Preview</label> <div className="p-4 border border-slate-200 dark:border-slate-800 rounded-xl space-y-3"> <button className="w-full px-4 py-2 text-white font-medium transition-colors" style={{ backgroundColor: theme.primaryColor.primary, borderRadius: theme.radius === "none" ? 0 : theme.radius === "sm" ? 4 : theme.radius === "md" ? 8 : theme.radius === "lg" ? 12 : 9999, fontSize: theme.fontSize }} > Primary Button </button> <div className="p-3 border border-slate-200 dark:border-slate-700" style={{ borderRadius: theme.radius === "none" ? 0 : theme.radius === "sm" ? 4 : theme.radius === "md" ? 8 : theme.radius === "lg" ? 12 : 9999 }} > <p style={{ fontSize: theme.fontSize }} className="text-slate-700 dark:text-slate-300">Sample text</p> </div> </div> </div> {/* Reset Button */} <button className="w-full px-4 py-2 border border-slate-200 dark:border-slate-700 rounded-lg text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"> Reset to Defaults </button> </div> </div> </> ); } function PaletteIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" /></svg>; } function XIcon() { return <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>; } function SunIcon() { return <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" /></svg>; } function MoonIcon() { return <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>; }