Skip to content

Theming ​

Customize the look and feel of Galaxy UI components to match your brand.

Overview ​

Galaxy UI uses CSS variables for theming, making it easy to customize colors and other design tokens across all components.

Default Theme ​

The default theme includes light and dark mode support:

css
@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
    --secondary: 210 40% 96.1%;
    --secondary-foreground: 222.2 47.4% 11.2%;
    --muted: 210 40% 96.1%;
    --muted-foreground: 215.4 16.3% 46.9%;
    --accent: 210 40% 96.1%;
    --accent-foreground: 222.2 47.4% 11.2%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 210 40% 98%;
    --border: 214.3 31.8% 91.4%;
    --input: 214.3 31.8% 91.4%;
    --ring: 222.2 84% 4.9%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --primary: 210 40% 98%;
    --primary-foreground: 222.2 47.4% 11.2%;
    --secondary: 217.2 32.6% 17.5%;
    --secondary-foreground: 210 40% 98%;
    --muted: 217.2 32.6% 17.5%;
    --muted-foreground: 215 20.2% 65.1%;
    --accent: 217.2 32.6% 17.5%;
    --accent-foreground: 210 40% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 210 40% 98%;
    --border: 217.2 32.6% 17.5%;
    --input: 217.2 32.6% 17.5%;
    --ring: 212.7 26.8% 83.9%;
  }
}

Color Variables ​

Understanding HSL Values ​

Colors are defined using HSL (Hue, Saturation, Lightness) values without the hsl() function:

css
/* ❌ Wrong */
--primary: hsl(222.2 47.4% 11.2%);

/* ✅ Correct */
--primary: 222.2 47.4% 11.2%;

The hsl() function is added in Tailwind config:

js
colors: {
  primary: "hsl(var(--primary))",
}

Core Colors ​

VariablePurposeExample Usage
--backgroundPage backgroundbg-background
--foregroundText colortext-foreground
--cardCard backgroundbg-card
--card-foregroundCard texttext-card-foreground
--primaryPrimary actionsButtons, links
--primary-foregroundPrimary textText on primary background
--secondarySecondary actionsSecondary buttons
--secondary-foregroundSecondary textText on secondary background
--mutedMuted backgroundSubtle backgrounds
--muted-foregroundMuted textPlaceholder text
--accentAccent backgroundHover states
--accent-foregroundAccent textText on accent background
--destructiveDestructive actionsDelete buttons
--destructive-foregroundDestructive textText on destructive background
--borderBorder colorComponent borders
--inputInput borderForm inputs
--ringFocus ringFocus states

Border Radius ​

css
--radius: 0.5rem; /* Default border radius */

Used in Tailwind config:

js
borderRadius: {
  lg: "var(--radius)",
  md: "calc(var(--radius) - 2px)",
  sm: "calc(var(--radius) - 4px)",
}

Creating Custom Themes ​

Method 1: CSS Variables ​

Create custom themes by overriding CSS variables:

css
@layer base {
  /* Custom theme */
  :root {
    --primary: 270 70% 55%; /* Purple */
    --primary-foreground: 0 0% 100%;
    --secondary: 340 80% 60%; /* Pink */
    --secondary-foreground: 0 0% 100%;
    --accent: 200 95% 50%; /* Blue */
    --accent-foreground: 0 0% 100%;
  }
}
vue
<template>
  <div>
    <Button variant="default">Primary Button</Button>
    <Button variant="secondary">Secondary Button</Button>
  </div>
</template>
tsx
export default function App() {
  return (
    <div>
      <Button variant="default">Primary Button</Button>
      <Button variant="secondary">Secondary Button</Button>
    </div>
  )
}

Method 2: Multiple Themes ​

Create multiple theme classes:

css
/* Theme 1: Ocean */
.theme-ocean {
  --primary: 200 95% 50%;
  --primary-foreground: 0 0% 100%;
  --secondary: 180 60% 45%;
  --secondary-foreground: 0 0% 100%;
}

/* Theme 2: Forest */
.theme-forest {
  --primary: 140 60% 45%;
  --primary-foreground: 0 0% 100%;
  --secondary: 120 40% 40%;
  --secondary-foreground: 0 0% 100%;
}

/* Theme 3: Sunset */
.theme-sunset {
  --primary: 25 95% 55%;
  --primary-foreground: 0 0% 100%;
  --secondary: 340 80% 60%;
  --secondary-foreground: 0 0% 100%;
}

Apply themes dynamically:

vue
<script setup lang="ts">
import { ref } from 'vue'

const theme = ref('theme-ocean')
</script>

<template>
  <div :class="theme">
    <select v-model="theme">
      <option value="theme-ocean">Ocean</option>
      <option value="theme-forest">Forest</option>
      <option value="theme-sunset">Sunset</option>
    </select>

    <Button>Themed Button</Button>
  </div>
</template>
tsx
import { useState } from 'react'
import { Button } from '@/components/ui/button'

export default function App() {
  const [theme, setTheme] = useState('theme-ocean')

  return (
    <div className={theme}>
      <select value={theme} onChange={(e) => setTheme(e.target.value)}>
        <option value="theme-ocean">Ocean</option>
        <option value="theme-forest">Forest</option>
        <option value="theme-sunset">Sunset</option>
      </select>

      <Button>Themed Button</Button>
    </div>
  )
}
typescript
import { Component, signal } from '@angular/core'
import { ButtonComponent } from '@/components/ui/button'

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ButtonComponent],
  template: `
    <div [class]="theme()">
      <select (change)="changeTheme($event)">
        <option value="theme-ocean">Ocean</option>
        <option value="theme-forest">Forest</option>
        <option value="theme-sunset">Sunset</option>
      </select>

      <ui-button>Themed Button</ui-button>
    </div>
  `
})
export class AppComponent {
  theme = signal('theme-ocean')

  changeTheme(event: Event) {
    const target = event.target as HTMLSelectElement
    this.theme.set(target.value)
  }
}

Method 3: Theme Generator ​

Use online tools to generate color schemes:

  1. Realtime Colors - realtimecolors.com
  2. Coolors - coolors.co
  3. Adobe Color - color.adobe.com

Convert colors to HSL format:

js
// Example: Convert hex to HSL
function hexToHSL(hex) {
  // Remove #
  hex = hex.replace(/^#/, '')

  // Convert to RGB
  const r = parseInt(hex.substring(0, 2), 16) / 255
  const g = parseInt(hex.substring(2, 4), 16) / 255
  const b = parseInt(hex.substring(4, 6), 16) / 255

  // Find min/max
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const diff = max - min

  // Calculate H, S, L
  let h = 0
  let s = 0
  const l = (max + min) / 2

  if (diff !== 0) {
    s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min)

    switch (max) {
      case r: h = ((g - b) / diff + (g < b ? 6 : 0)) / 6; break
      case g: h = ((b - r) / diff + 2) / 6; break
      case b: h = ((r - g) / diff + 4) / 6; break
    }
  }

  return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
}

// Usage
hexToHSL('#3b82f6') // "217 91% 60%"

Component-Specific Styling ​

Override Component Classes ​

All components accept className prop:

vue
<Button class="bg-gradient-to-r from-purple-500 to-pink-500">
  Gradient Button
</Button>
tsx
<Button className="bg-gradient-to-r from-purple-500 to-pink-500">
  Gradient Button
</Button>
typescript
<ui-button class="bg-gradient-to-r from-purple-500 to-pink-500">
  Gradient Button
</ui-button>

Custom Variants ​

Modify component variants file:

typescript
// components/ui/button/variants.ts
import { cva } from 'class-variance-authority'

export const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
        outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',

        // ✨ Add custom variant
        gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
)

Use custom variant:

vue
<Button variant="gradient">Custom Gradient</Button>

Best Practices ​

1. Use Semantic Colors ​

css
/* ✅ Good: Semantic naming */
--primary: 270 70% 55%;
--success: 140 60% 45%;
--warning: 45 95% 55%;
--error: 0 84% 60%;

/* ❌ Bad: Color-specific naming */
--blue: 217 91% 60%;
--green: 140 60% 45%;
--yellow: 45 95% 55%;
--red: 0 84% 60%;

2. Maintain Contrast Ratios ​

Ensure text is readable (WCAG AA compliance):

  • Normal text: Minimum 4.5:1 contrast ratio
  • Large text: Minimum 3:1 contrast ratio

Use tools like WebAIM Contrast Checker

3. Test Both Light and Dark Modes ​

css
:root {
  --primary: 270 70% 55%;
}

.dark {
  /* Adjust lightness for dark mode */
  --primary: 270 70% 65%;
}

4. Document Your Theme ​

Create a theme configuration file:

typescript
// config/theme.ts
export const theme = {
  colors: {
    primary: {
      hsl: '270 70% 55%',
      name: 'Purple',
      usage: 'Primary actions, links',
    },
    secondary: {
      hsl: '340 80% 60%',
      name: 'Pink',
      usage: 'Secondary actions',
    },
  },
  borderRadius: {
    default: '0.5rem',
    usage: 'Cards, buttons, inputs',
  },
}

Examples ​

Brand Theme ​

css
/* Corporate brand colors */
:root {
  --primary: 210 100% 50%; /* Brand blue */
  --primary-foreground: 0 0% 100%;
  --secondary: 150 60% 50%; /* Brand green */
  --secondary-foreground: 0 0% 100%;
  --accent: 30 100% 50%; /* Brand orange */
  --accent-foreground: 0 0% 100%;
}

Minimal Theme ​

css
/* Minimal, neutral theme */
:root {
  --primary: 0 0% 10%;
  --primary-foreground: 0 0% 100%;
  --secondary: 0 0% 96%;
  --secondary-foreground: 0 0% 10%;
  --accent: 0 0% 90%;
  --accent-foreground: 0 0% 10%;
  --radius: 0.25rem; /* Smaller radius */
}

Vibrant Theme ​

css
/* Colorful, vibrant theme */
:root {
  --primary: 280 100% 60%; /* Vibrant purple */
  --primary-foreground: 0 0% 100%;
  --secondary: 340 100% 65%; /* Vibrant pink */
  --secondary-foreground: 0 0% 100%;
  --accent: 180 100% 50%; /* Vibrant cyan */
  --accent-foreground: 0 0% 10%;
  --radius: 1rem; /* Larger radius */
}

Resources ​

Next Steps ​

Released under the MIT License.