Dark Mode
Overview
The POS system supports dark mode through PrimeVue 4's built-in theming system. Dark mode is toggled by adding or removing the dark CSS class on the <html> element. PrimeVue's Aura theme automatically adapts all component styles when this class is present.
How It Works
PrimeVue Theme Configuration
The theme is configured in apps/web-client/src/main.ts:
app.use(PrimeVue, {
theme: {
preset: posTheme,
options: {
darkModeSelector: '.dark',
},
},
});The darkModeSelector: '.dark' tells PrimeVue to apply dark mode styles when the .dark class is present on any ancestor element (applied to <html>).
Custom Theme Preset
The theme preset extends PrimeVue's Aura base theme with blue as the primary color:
// packages/ui-kit/src/theme/index.ts
import { definePreset } from '@primevue/themes';
import Aura from '@primevue/themes/aura';
export const posTheme = definePreset(Aura, {
semantic: {
primary: {
50: '{blue.50}',
100: '{blue.100}',
// ... all shades
950: '{blue.950}',
},
},
});useTheme Composable
The useTheme composable manages the dark mode state:
// apps/web-client/src/composables/useTheme.ts
import { ref, watch } from 'vue';
const STORAGE_KEY = 'pos-theme';
type Theme = 'light' | 'dark';
const currentTheme = ref<Theme>(
(localStorage.getItem(STORAGE_KEY) as Theme) || 'light'
);
function applyTheme(theme: Theme) {
document.documentElement.classList.toggle('dark', theme === 'dark');
}
// Apply on load
applyTheme(currentTheme.value);
watch(currentTheme, (val) => {
localStorage.setItem(STORAGE_KEY, val);
applyTheme(val);
});
export function useTheme() {
function toggle() {
currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light';
}
return {
theme: currentTheme,
isDark: () => currentTheme.value === 'dark',
toggle,
};
}Key behaviors:
- Theme preference persists in
localStorageunder the keypos-theme - Default is
lightmode - The
.darkclass is toggled ondocument.documentElement(the<html>element) - The composable uses a module-level
ref, so theme state is shared across all components that import it
Toggle Button
Layouts include a theme toggle button, typically in the header:
<script setup>
import { useTheme } from '@/composables/useTheme';
const { isDark, toggle } = useTheme();
</script>
<template>
<Button
:icon="isDark() ? 'pi pi-sun' : 'pi pi-moon'"
@click="toggle"
text
rounded
/>
</template>CSS Variables for Custom Styling
When writing custom styles that need to respect dark mode, use PrimeVue's CSS custom properties instead of hardcoded colors. PrimeVue automatically updates these variables when dark mode is toggled.
Recommended Variables
| Variable | Light Value | Dark Value | Use For |
|---|---|---|---|
--p-content-background | white | dark gray | Page/card backgrounds |
--p-text-color | dark gray | light gray | Primary text |
--p-text-muted-color | medium gray | medium gray | Secondary text |
--p-content-border-color | light gray | dark gray | Borders |
--p-highlight-background | light blue | dark blue | Selected/active items |
--p-highlight-color | blue | light blue | Selected item text |
--p-surface-0 through --p-surface-950 | white to black | black to white | Surface shades |
--p-primary-color | blue-500 | blue-400 | Primary action color |
--p-primary-contrast-color | white | white | Text on primary background |
Usage Examples
/* Background that adapts to dark mode */
.my-card {
background: var(--p-content-background);
color: var(--p-text-color);
border: 1px solid var(--p-content-border-color);
}
/* Colored background that works in both modes */
.status-badge {
background: color-mix(in srgb, var(--p-primary-color) 15%, transparent);
color: var(--p-primary-color);
}
/* Hover state */
.my-item:hover {
background: var(--p-highlight-background);
color: var(--p-highlight-color);
}Using color-mix for Colored Backgrounds
When you need a tinted background (e.g., a light blue card on white, dark blue card on dark), use CSS color-mix with a PrimeVue variable:
/* 15% of primary color mixed with transparent = subtle tint in both modes */
background: color-mix(in srgb, var(--p-primary-color) 15%, transparent);
/* For semantic colors */
background: color-mix(in srgb, var(--p-green-500) 10%, transparent);This technique works in both light and dark modes because color-mix computes the result relative to the current value of the CSS variable.
Rules
Do
- Use PrimeVue CSS variables (
--p-*) for all colors - Use
color-mix()for tinted/subtle colored backgrounds - Test both light and dark modes when adding new views
- Use PrimeVue's semantic tokens (
--p-content-*,--p-text-*,--p-highlight-*)
Do Not
- Do not use hardcoded color values (
#ffffff,rgb(0,0,0)) - Do not use PrimeVue's primitive color tokens directly (
--p-blue-500). Use semantic tokens (--p-primary-color) that automatically adapt to the theme - Do not add separate
.darkCSS rules when a CSS variable would work - Do not use Tailwind's
dark:variant (the project uses PrimeVue's theming system, not Tailwind's dark mode)
Exception
Primitive tokens (--p-blue-500, --p-green-500) are acceptable in color-mix() for status indicators where the color has semantic meaning independent of the theme (e.g., green for success, red for error).