Theming Guide

June 22, 2026 · View on GitHub

This document provides a comprehensive guide for understanding and customizing the theming system in LibreDB Studio.

Overview

LibreDB Studio uses a modern theming architecture built on:

  • Tailwind CSS v4 - CSS-first configuration with @theme directive
  • shadcn/ui - Accessible component library with CSS variable theming
  • CSS Custom Properties - Light and dark variable sets defined in globals.css

Note: Studio currently ships dark-mode only. The .dark class is applied statically on the <body> element in src/app/layout.tsx, so the light-mode variables defined in :root are present but not reachable at runtime. There is no theme toggle yet (see Switching Themes). The light-mode values are documented below for when runtime switching is added.

Architecture

Theme Configuration Flow

globals.css

    ├── :root (Light mode variables)
    ├── .dark (Dark mode variables)

    └── @theme inline

            └── Maps CSS variables to Tailwind utilities

                    └── bg-background, text-foreground, etc.

File Structure

src/
└── app/
    └── globals.css          # Theme configuration (single source of truth)

CSS Variables

Core Variables

VariableDescriptionUsage
--backgroundPage background colorbg-background
--foregroundDefault text colortext-foreground
--cardCard/panel backgroundbg-card
--card-foregroundCard text colortext-card-foreground
--popoverPopover/dropdown backgroundbg-popover
--popover-foregroundPopover text colortext-popover-foreground
--primaryPrimary action colorbg-primary, text-primary
--primary-foregroundText on primarytext-primary-foreground
--secondarySecondary action colorbg-secondary
--secondary-foregroundText on secondarytext-secondary-foreground
--mutedMuted/subtle backgroundbg-muted
--muted-foregroundMuted text colortext-muted-foreground
--accentAccent/hover backgroundbg-accent
--accent-foregroundText on accenttext-accent-foreground
--destructiveDestructive action colorbg-destructive
--destructive-foregroundText on destructivetext-destructive-foreground
--borderBorder colorborder-border
--inputInput border colorborder-input
--ringFocus ring colorring-ring
--radiusBorder radius baserounded-lg, rounded-md

Chart Colors

VariableLight (:root)Dark (.dark)Usage
--chart-1#e76e50#3b82f6Primary chart color
--chart-2#2a9d90#22c55eSecondary chart color
--chart-3#274754#f59e0bTertiary chart color
--chart-4#e8c468#a855f7Quaternary chart color
--chart-5#f4a462#ec4899Quinary chart color

Dark Mode

Current Configuration

LibreDB Studio uses a dark-first design with the following color palette (based on Tailwind Zinc):

.dark {
  --background: #09090b;      /* zinc-950 */
  --foreground: #fafafa;      /* zinc-50 */
  --card: #0a0a0a;            /* near zinc-950 */
  --popover: #0a0a0a;
  --secondary: #27272a;       /* zinc-800 */
  --muted: #27272a;           /* zinc-800 */
  --accent: #27272a;          /* zinc-800 */
  --border: #27272a;          /* zinc-800 */
  --muted-foreground: #a1a1aa; /* zinc-400 */
}

Switching Themes

Current state: there is no runtime theme switching. Dark mode is forced by hardcoding the dark class on <body> in src/app/layout.tsx:

<body className={`${geistSans.variable} ${geistMono.variable} antialiased dark font-sans`}>

The next-themes package is present in package.json but is not wired up — there is no <ThemeProvider> in the layout and no toggle component.

To add a runtime light/dark toggle, you would wrap the app in next-themes' ThemeProvider (attribute="class") instead of hardcoding the class, then add a toggle that flips the theme:

// Not yet implemented — illustrative only
<ThemeProvider attribute="class" defaultTheme="dark">
  {children}
</ThemeProvider>

Tailwind v4 Integration

The @theme inline Directive

Tailwind CSS v4 introduces CSS-first configuration. The @theme inline directive maps CSS variables to Tailwind utility classes:

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  /* ... */
}

This enables using semantic class names:

<div className="bg-background text-foreground">
  <div className="bg-card border-border">
    Content
  </div>
</div>

IDE Warnings

Your IDE may show warnings like Unknown at rule @theme. This is expected because:

  • Tailwind v4's @theme directive is new
  • CSS validators don't recognize it yet
  • It works correctly - the build succeeds

To suppress these warnings in VS Code, add to .vscode/settings.json:

{
  "css.lint.unknownAtRules": "ignore"
}

Best Practices

DO Use Theme Variables

// Good - uses theme variables
<div className="bg-background text-foreground border-border">
<span className="text-muted-foreground">
<button className="bg-primary text-primary-foreground hover:bg-accent">

DON'T Use Hardcoded Colors

// Bad - hardcoded colors
<div className="bg-[#050505] text-white border-[#262626]">
<span className="text-zinc-500">
<button className="bg-zinc-900 hover:bg-zinc-800">

Opacity Modifiers

Use opacity modifiers with theme variables:

<div className="bg-accent/50">        {/* 50% opacity */}
<span className="text-muted-foreground/70">  {/* 70% opacity */}
<div className="border-border/30">    {/* 30% opacity */}

Customizing the Theme

Step 1: Modify CSS Variables

Edit src/app/globals.css:

.dark {
  /* Change the primary color */
  --primary: #3b82f6;  /* blue-500 */
  --primary-foreground: #ffffff;

  /* Change the accent color */
  --accent: #1e3a5f;
}

Step 2: Verify Mappings

Ensure @theme inline maps your variables:

@theme inline {
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-accent: var(--accent);
}

Step 3: Test in Dark Mode

Since Studio runs dark-mode only today, edit and verify the .dark variable set. If you also maintain the :root (light) values for a future toggle, keep them in sync — but only the .dark set is rendered at runtime.

Component-Specific Theming

Buttons

shadcn/ui buttons use theme variables automatically:

<Button variant="default">   {/* bg-primary */}
<Button variant="secondary"> {/* bg-secondary */}
<Button variant="outline">   {/* border-input */}
<Button variant="ghost">     {/* hover:bg-accent */}
<Button variant="destructive"> {/* bg-destructive */}

Cards

<Card>  {/* bg-card border-border */}
  <CardHeader>
    <CardTitle>   {/* text-card-foreground */}
<DropdownMenuContent>  {/* bg-popover text-popover-foreground */}

Inputs

<Input>  {/* bg-background border-input */}

Adding New Colors

Step 1: Define Variables

:root {
  --warning: #f59e0b;
  --warning-foreground: #ffffff;
}

.dark {
  --warning: #d97706;
  --warning-foreground: #ffffff;
}

Step 2: Add Theme Mapping

@theme inline {
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
}

Step 3: Use in Components

<div className="bg-warning text-warning-foreground">
  Warning message
</div>

Troubleshooting

Colors Not Applying

  1. Check that the variable is defined in both :root and .dark
  2. Verify the @theme inline mapping exists
  3. Ensure you're using the correct class name (bg-card not bg-[--card])

Dark Mode Not Working

  1. Check that the dark class is present on <body> in src/app/layout.tsx (it is hardcoded there)
  2. Ensure variables are defined in the .dark {} selector in globals.css
  3. Confirm @theme inline maps the variable to a --color-* utility

Build Errors

  1. Run bun run build to check for CSS syntax errors
  2. Verify all variables are properly closed
  3. Check for typos in variable names

Resources

Official Documentation

Theme Generators

Color References

Color Palette Reference

Light Mode (Default)

VariableHexDescription
background#ffffffWhite
foreground#0a0a0aNear black
card#ffffffWhite
primary#171717Near black
secondary#f5f5f5Light gray
muted#f5f5f5Light gray
muted-foreground#737373Medium gray
accent#f5f5f5Light gray
border#e5e5e5Gray

Dark Mode

VariableHexTailwindDescription
background#09090bzinc-950Near black
foreground#fafafazinc-50Near white
card#0a0a0a-Dark
primary#fafafazinc-50Near white
secondary#27272azinc-800Dark gray
muted#27272azinc-800Dark gray
accent#27272azinc-800Dark gray
border#27272azinc-800Dark gray
muted-foreground#a1a1aazinc-400Medium gray

Last updated: June 2026