Overview
Tinte is an AI-powered theme editor that generates beautiful, consistent themes for shadcn/ui using semantic color palettes in OKLCH color space. Live-edit your design tokens with real-time preview, intelligent color format preservation, and AI-assisted theme generation.
🏆 Winner of 3rd Place at Vercel × Midudev Hackathon
Tinte provides a floating ball UI that expands into a full theme editor, supporting HEX, RGB, HSL, OKLCH, and LCH color formats with automatic format preservation.
Components
Theme Editor
Live theme editor with AI generation, format preservation, and real-time preview.
Features: AI theme generation, OKLCH/HEX/RGB/HSL/LCH support, live preview, format preservation
import { TinteEditor } from "@elements/tinte";
export default function App() {
return (
<div>
<YourApp />
<TinteEditor />
</div>
);
}
Quick Start
1. Install
bunx shadcn@latest add @elements/tinte-editor
This installs:
- TinteEditor - Main floating theme editor component
- ColorInput - Color picker with format switching
- TinteLogo - Tinte logo component
- API Routes - Endpoints for reading/writing globals.css
2. Add to Layout
// app/layout.tsx
import { TinteEditor } from "@elements/tinte";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<TinteEditor />
</body>
</html>
);
}
3. Start Editing
Click the floating purple ball in the bottom-right corner to open the theme editor.
Features
🎨 Real-time Color Editing
Edit any shadcn/ui design token with instant visual feedback. Changes apply immediately to your UI.
🌈 Color Format Preservation
Automatically preserves your original color format:
- OKLCH → stays OKLCH
- HEX → stays HEX
- RGB → stays RGB
- HSL → stays HSL
- LCH → stays LCH
🎯 Format Switching
Click the format button next to any color input to cycle through formats:
HEX → RGB → HSL → OKLCH → LCH → HEX
🌓 Light/Dark Mode Toggle
Switch between light and dark mode to edit theme variables for each mode independently.
📦 Organized Token Groups
Tokens are organized into semantic groups:
- Background & Text - Core surface colors
- Cards & Surfaces - Muted and accent colors
- Interactive Elements - Primary and secondary colors
- Forms & States - Input, border, ring, destructive
- Charts - Data visualization colors
- Sidebar - Sidebar-specific tokens
💾 Save to globals.css
Save your changes directly to app/globals.css
with one click.
🔄 Reload Theme
Reset to the current values in app/globals.css
at any time.
Configuration
Conditional Rendering (Recommended)
Only show the theme editor in development:
// app/layout.tsx
import { TinteEditor } from "@elements/tinte";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
{process.env.NODE_ENV === "development" && <TinteEditor />}
</body>
</html>
);
}
Color Input Component
Use the ColorInput
component standalone:
import ColorInput from "@/components/color-input";
export default function CustomForm() {
const [color, setColor] = useState("oklch(0.7 0.15 180)");
return (
<ColorInput
value={color}
onChange={setColor}
/>
);
}
Features:
- Color wheel picker
- Text input with validation
- Format toggle button
- Click-outside to close
API Routes
The theme editor requires two API endpoints to read and write globals.css
:
Read Endpoint
// app/api/tinte/read-globals/route.ts
import { NextResponse } from "next/server";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
export async function GET() {
const globalsPath = join(process.cwd(), "app", "globals.css");
const content = await readFile(globalsPath, "utf-8");
return NextResponse.json({ success: true, content });
}
Write Endpoint
// app/api/tinte/write-globals/route.ts
import { NextResponse } from "next/server";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
export async function POST(request: Request) {
const { content } = await request.json();
const globalsPath = join(process.cwd(), "app", "globals.css");
await writeFile(globalsPath, content, "utf-8");
return NextResponse.json({ success: true });
}
Both routes are automatically installed with the component.
Token Reference
Background & Text
Token | Description |
---|---|
--background | Main background color |
--foreground | Main text color |
--card | Card background |
--card-foreground | Card text |
--popover | Popover background |
--popover-foreground | Popover text |
Cards & Surfaces
Token | Description |
---|---|
--muted | Muted background |
--muted-foreground | Muted text |
--accent | Accent background |
--accent-foreground | Accent text |
Interactive Elements
Token | Description |
---|---|
--primary | Primary button background |
--primary-foreground | Primary button text |
--secondary | Secondary button background |
--secondary-foreground | Secondary button text |
Forms & States
Token | Description |
---|---|
--destructive | Destructive action background |
--destructive-foreground | Destructive action text |
--input | Input border |
--ring | Focus ring |
--border | Border color |
Charts
Token | Description |
---|---|
--chart-1 through --chart-5 | Chart data colors |
Sidebar
Token | Description |
---|---|
--sidebar-background | Sidebar background |
--sidebar-foreground | Sidebar text |
--sidebar-primary | Sidebar primary background |
--sidebar-primary-foreground | Sidebar primary text |
--sidebar-accent | Sidebar accent background |
--sidebar-accent-foreground | Sidebar accent text |
--sidebar-border | Sidebar border |
--sidebar-ring | Sidebar focus ring |
How It Works
- Read globals.css - Fetches current theme variables via API
- Parse CSS - Extracts
:root
and.dark
variables - Live Updates - Changes apply to
document.documentElement.style
- Save - Writes updated CSS back to
globals.css
Color Format Details
OKLCH (Recommended)
--primary: oklch(0.7 0.15 270);
Benefits: Perceptually uniform, wide gamut, human-readable
HEX
--primary: #6366f1;
Benefits: Compact, widely supported, familiar
RGB
--primary: rgb(99, 102, 241);
Benefits: Direct browser support, alpha channel
HSL
--primary: hsl(239, 84%, 67%);
Benefits: Intuitive (hue, saturation, lightness)
LCH
--primary: lch(55% 75 270);
Benefits: Perceptually uniform like OKLCH
Best Practices
- Development Only - Use conditional rendering (see Configuration above)
- Version Control - Commit
globals.css
changes carefully - Test Both Modes - Always verify light and dark mode
- Use OKLCH - For consistent color manipulation across modes
- Semantic Tokens - Edit tokens, not component colors directly
Troubleshooting
API routes return 500: Verify app/globals.css
exists and is writable
Colors not updating: Check browser console for CORS or API errors
Format not preserved: Ensure color value is valid CSS
Floating ball hidden: Check z-index
conflicts (ball uses z-50
)
Save fails: Verify file permissions on app/globals.css
Security Considerations
⚠️ Production Warning: The write API endpoint allows modifying globals.css
. Never expose in production.
Disable in Production
// app/api/tinte/write-globals/route.ts
export async function POST(request: Request) {
if (process.env.NODE_ENV === "production") {
return NextResponse.json(
{ success: false, error: "Disabled in production" },
{ status: 403 }
);
}
// ... rest of implementation
}
Keyboard Shortcuts
Key | Action |
---|---|
Esc | Close theme editor |
Tab | Navigate between inputs |
Enter | Apply color in input field |
Dependencies
- culori - Color conversion and manipulation
- @uiw/react-color - Color picker wheel component
- @uiw/color-convert - Color format conversions
- lucide-react - Icons (X, RotateCw, Save)
- sonner - Toast notifications