Command Palette

Search for a command to run...

[ COMPONENTS ]
Tinte

Tinte

tinte components

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.

Check the bottom right corner

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

TokenDescription
--backgroundMain background color
--foregroundMain text color
--cardCard background
--card-foregroundCard text
--popoverPopover background
--popover-foregroundPopover text

Cards & Surfaces

TokenDescription
--mutedMuted background
--muted-foregroundMuted text
--accentAccent background
--accent-foregroundAccent text

Interactive Elements

TokenDescription
--primaryPrimary button background
--primary-foregroundPrimary button text
--secondarySecondary button background
--secondary-foregroundSecondary button text

Forms & States

TokenDescription
--destructiveDestructive action background
--destructive-foregroundDestructive action text
--inputInput border
--ringFocus ring
--borderBorder color

Charts

TokenDescription
--chart-1 through --chart-5Chart data colors

Sidebar

TokenDescription
--sidebar-backgroundSidebar background
--sidebar-foregroundSidebar text
--sidebar-primarySidebar primary background
--sidebar-primary-foregroundSidebar primary text
--sidebar-accentSidebar accent background
--sidebar-accent-foregroundSidebar accent text
--sidebar-borderSidebar border
--sidebar-ringSidebar focus ring

How It Works

  1. Read globals.css - Fetches current theme variables via API
  2. Parse CSS - Extracts :root and .dark variables
  3. Live Updates - Changes apply to document.documentElement.style
  4. 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

  1. Development Only - Use conditional rendering (see Configuration above)
  2. Version Control - Commit globals.css changes carefully
  3. Test Both Modes - Always verify light and dark mode
  4. Use OKLCH - For consistent color manipulation across modes
  5. 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

KeyAction
EscClose theme editor
TabNavigate between inputs
EnterApply 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

Resources