Theme Customization
Learn how to create custom themes for the UI Kit.
Basic Customization
Override Variables
The simplest way to customize is to override CSS variables after importing the styles:
// main.ts
import '@gxp-dev/uikit/styles'
import './my-overrides.css'
/* my-overrides.css */
:root {
--primary: #0066cc;
--primary-foreground: #ffffff;
--radius: 0.25rem;
}
Start Fresh
For complete control, import only base styles:
// main.ts
import '@gxp-dev/uikit/styles/base'
import './my-theme.css'
Creating a Custom Theme
Minimal Theme
A minimal theme needs these core variables:
/* minimal-theme.css */
:root {
/* Page */
--background: #ffffff;
--foreground: #0f172a;
/* Primary */
--primary: #2563eb;
--primary-foreground: #ffffff;
/* Secondary */
--secondary: #f1f5f9;
--secondary-foreground: #0f172a;
/* Borders & Inputs */
--border: #e2e8f0;
--input: #ffffff;
--ring: #2563eb;
/* Radius */
--radius: 0.5rem;
}
Complete Theme
A complete theme with all variables:
/* complete-theme.css */
:root {
/* Page backgrounds */
--background: #ffffff;
--foreground: #0f172a;
/* Card/Modal surfaces */
--card: #ffffff;
--card-foreground: #0f172a;
--popover: #ffffff;
--popover-foreground: #0f172a;
/* Primary actions */
--primary: #2563eb;
--primary-foreground: #ffffff;
/* Secondary actions */
--secondary: #f1f5f9;
--secondary-foreground: #0f172a;
/* Muted/subtle elements */
--muted: #f1f5f9;
--muted-foreground: #64748b;
/* Accents/highlights */
--accent: #f1f5f9;
--accent-foreground: #0f172a;
/* Destructive/danger */
--destructive: #dc2626;
--destructive-foreground: #ffffff;
/* Form elements */
--border: #e2e8f0;
--input: #ffffff;
--ring: #2563eb;
/* Border radius */
--radius: 0.5rem;
}
Theme Examples
Light Corporate Theme
:root {
--background: #f8fafc;
--foreground: #1e293b;
--card: #ffffff;
--card-foreground: #1e293b;
--popover: #ffffff;
--popover-foreground: #1e293b;
--primary: #0f766e;
--primary-foreground: #ffffff;
--secondary: #e2e8f0;
--secondary-foreground: #1e293b;
--muted: #f1f5f9;
--muted-foreground: #64748b;
--accent: #ccfbf1;
--accent-foreground: #0f766e;
--destructive: #b91c1c;
--destructive-foreground: #ffffff;
--border: #cbd5e1;
--input: #ffffff;
--ring: #0f766e;
--radius: 0.375rem;
}
Dark Theme
:root {
--background: #0f172a;
--foreground: #f8fafc;
--card: #1e293b;
--card-foreground: #f8fafc;
--popover: #1e293b;
--popover-foreground: #f8fafc;
--primary: #3b82f6;
--primary-foreground: #ffffff;
--secondary: #334155;
--secondary-foreground: #f8fafc;
--muted: #334155;
--muted-foreground: #94a3b8;
--accent: #334155;
--accent-foreground: #f8fafc;
--destructive: #ef4444;
--destructive-foreground: #ffffff;
--border: #334155;
--input: #1e293b;
--ring: #3b82f6;
--radius: 0.5rem;
}
Brand Color Theme
/* Example: Purple brand */
:root {
--background: #faf5ff;
--foreground: #1e1b4b;
--card: #ffffff;
--card-foreground: #1e1b4b;
--popover: #ffffff;
--popover-foreground: #1e1b4b;
--primary: #7c3aed;
--primary-foreground: #ffffff;
--secondary: #ede9fe;
--secondary-foreground: #5b21b6;
--muted: #f5f3ff;
--muted-foreground: #6b7280;
--accent: #ddd6fe;
--accent-foreground: #5b21b6;
--destructive: #dc2626;
--destructive-foreground: #ffffff;
--border: #c4b5fd;
--input: #ffffff;
--ring: #7c3aed;
--radius: 0.75rem;
}
Dynamic Theming
JavaScript Theme Switching
function setTheme(theme: 'light' | 'dark') {
const root = document.documentElement
if (theme === 'dark') {
root.style.setProperty('--background', '#0f172a')
root.style.setProperty('--foreground', '#f8fafc')
// ... set other dark variables
} else {
root.style.setProperty('--background', '#ffffff')
root.style.setProperty('--foreground', '#0f172a')
// ... set other light variables
}
}
Theme from API
interface ThemeConfig {
backgroundColor: string
textColor: string
primaryColor: string
primaryTextColor: string
// ...
}
function applyTheme(config: ThemeConfig) {
const root = document.documentElement
root.style.setProperty('--background', config.backgroundColor)
root.style.setProperty('--foreground', config.textColor)
root.style.setProperty('--primary', config.primaryColor)
root.style.setProperty('--primary-foreground', config.primaryTextColor)
// ... apply other variables
}
// Fetch and apply theme
const theme = await fetchTheme()
applyTheme(theme)
Platform Integration
When used within the GXP platform, themes are applied automatically:
// Platform handles this
watch(platformTheme, (theme) => {
document.documentElement.style.setProperty('--page_background_color', theme.backgroundColor)
document.documentElement.style.setProperty('--primary_button_background_color', theme.primaryColor)
// The shadcn variables reference platform variables, so they update automatically
})
Component-Level Overrides
Scoped Styling
Override variables for specific components:
<template>
<div class="special-section">
<Button>Special Button</Button>
</div>
</template>
<style scoped>
.special-section {
--primary: #dc2626;
--primary-foreground: #ffffff;
}
</style>
Utility Classes
Use Tailwind's arbitrary value syntax:
<template>
<Button class="bg-[#dc2626] hover:bg-[#b91c1c] text-white">
Custom Red Button
</Button>
</template>
Best Practices
1. Use Semantic Colors
Don't use colors directly; use semantic variables:
/* Good */
.my-component {
background: var(--background);
color: var(--foreground);
}
/* Avoid */
.my-component {
background: #ffffff;
color: #000000;
}
2. Maintain Contrast
Ensure sufficient contrast between foreground and background:
/* Primary and primary-foreground must have good contrast */
--primary: #2563eb; /* Blue */
--primary-foreground: #ffffff; /* White - high contrast */
3. Test Both Themes
If supporting light/dark modes, test components in both:
/* Light mode variables */
:root {
--card: #ffffff;
--card-foreground: #0f172a;
}
/* Dark mode variables */
.dark {
--card: #1e293b;
--card-foreground: #f8fafc;
}
4. Document Custom Themes
When creating a custom theme, document the color palette:
/*
* Brand Theme - Acme Corp
* Primary: Acme Blue (#0066cc)
* Secondary: Light Gray (#f5f5f5)
* Accent: Teal (#0d9488)
* Destructive: Red (#dc2626)
*/