Dark Mode
DocBits components support full dark mode out of the box with automatic color switching and seamless theme transitions.
Overview
Dark mode is implemented as a first-class feature:
- Automatic: All components automatically support dark mode
- Easy to Enable: Single CSS class toggles dark mode
- Customizable: All colors are CSS variables
- Accessible: Full WCAG 2.1 AA compliance in both modes
- Performant: No JavaScript required for theme switching
Enabling Dark Mode
Method 1: CSS Class
Toggle dark mode by adding a class to the root element:
<!-- Light mode (default) -->
<html>
<body><!-- Light colors --></body>
</html>
<!-- Dark mode -->
<html>
<body class="dark"><!-- Dark colors --></body>
</html>Method 2: Data Attribute
Alternative approach using data attributes:
<!-- Light mode (default) -->
<html data-theme="light">
<body><!-- Light colors --></body>
</html>
<!-- Dark mode -->
<html data-theme="dark">
<body><!-- Dark colors --></body>
</html>Method 3: CSS Prefers-Color-Scheme
Automatically respect system preferences:
/* Light mode (default) */
:root {
--q-primary: #2388AE;
--dashboard-text-color: #707070;
}
/* Dark mode via system preference */
@media (prefers-color-scheme: dark) {
:root {
--q-primary: #34eea9;
--dashboard-text-color: #b0b0b0;
}
}Vue Component Example
<template>
<div class="app" :class="{ dark: isDarkMode }">
<button @click="toggleDarkMode">
{{ isDarkMode ? '☀️' : '🌙' }}
</button>
<!-- App content -->
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
const isDarkMode = ref(false)
// Detect system preference on mount
onMounted(() => {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
isDarkMode.value = prefersDark
})
// Listen for system preference changes
watch(isDarkMode, (newValue) => {
// Save to localStorage
localStorage.setItem('theme', newValue ? 'dark' : 'light')
// Update DOM
document.documentElement.classList.toggle('dark', newValue)
})
function toggleDarkMode() {
isDarkMode.value = !isDarkMode.value
}
</script>
<style scoped>
.app {
background-color: var(--dashboard-surface-color);
color: var(--dashboard-text-color);
transition: background-color 0.3s, color 0.3s;
}
</style>Color Changes
Primary Colors
| Mode | Color | Hex |
|---|---|---|
| Light | Primary Blue | #2388AE |
| Dark | Secondary Green | #34eea9 |
The primary color automatically inverts for dark mode to maintain visibility and brand consistency.
Table Colors
Light Mode:
- Header: #ffffff background, #313131 text
- Even rows: #fafbfc background
- Odd rows: #ffffff background
- Hover: rgba(35, 136, 174, 0.08) background
Dark Mode:
- Header: #292929 background, #e4e4e4 text
- Even rows: #2a2a2a background
- Odd rows: #383838 background
- Hover: rgba(35, 136, 174, 0.3) background
Text Colors
| Element | Light | Dark |
|---|---|---|
| Primary Text | #707070 | #b0b0b0 |
| Disabled Text | #aeaeae | #5e5e5e |
| Borders | #e9e9e9 | #5e5e5e |
| Surface | #ffffff | #1a1a1a |
CSS Variables
All colors use CSS variables for easy theme customization:
/* Light mode (default) */
:root {
/* Brand Colors */
--q-primary: #2388AE;
--q-secondary: #34eea9;
--q-accent: #9999d6;
/* Status Colors */
--docbits-positive: #55b559;
--docbits-negative: #d00000;
--docbits-warning: #f2c037;
--docbits-info: #31ccec;
/* Dashboard Colors */
--dashboard-text-color: #707070;
--dashboard-text-disabled: #aeaeae;
--dashboard-border-color: #aeaeae;
--dashboard-line-color: #e9e9e9;
--dashboard-surface-color: #ffffff;
/* Table Colors */
--dashboard-table-header-text: #313131;
--dashboard-table-header-bg: #ffffff;
--dashboard-table-row-even-bg: #fafbfc;
--dashboard-table-row-odd-bg: #ffffff;
--dashboard-table-row-hover-bg: rgba(35, 136, 174, 0.08);
}
/* Dark mode */
body.dark {
--q-primary: #34eea9;
--q-secondary: #2388AE;
--dashboard-text-color: #b0b0b0;
--dashboard-text-disabled: #5e5e5e;
--dashboard-border-color: #b0b0b0;
--dashboard-line-color: #5e5e5e;
--dashboard-surface-color: #1a1a1a;
--dashboard-table-header-text: #e4e4e4;
--dashboard-table-header-bg: #292929;
--dashboard-table-row-even-bg: #2a2a2a;
--dashboard-table-row-odd-bg: #383838;
--dashboard-table-row-hover-bg: rgba(35, 136, 174, 0.3);
}Component Theme Support
All DocBits components automatically adapt to dark mode:
DocBitsTable
<template>
<!-- Automatically uses dark mode colors when dark class is present -->
<DocBitsTable
:rows="documents"
:columns="columns"
row-key="id"
/>
</template>ColumnOrderBy
<template>
<!-- Sort arrows automatically change color -->
<ColumnOrderBy
column="name"
:current-sort="sortColumn"
:current-direction="sortDirection"
@sort="handleSort"
/>
</template>BulkActionsMenu
<template>
<!-- FAB and action menu adapt to dark mode -->
<BulkActionsMenu
:selected-count="selectedCount"
@export="handleExport"
@delete="handleDelete"
/>
</template>Theme Customization
Override Default Colors
Create custom theme by overriding CSS variables:
/* Custom light theme */
:root {
--q-primary: #1976d2; /* Custom blue */
--q-secondary: #26a69a; /* Custom teal */
--dashboard-text-color: #333; /* Darker text */
--dashboard-surface-color: #fafafa;
}
/* Custom dark theme */
body.dark {
--q-primary: #64b5f6; /* Light blue */
--q-secondary: #4db6ac; /* Light teal */
--dashboard-text-color: #e0e0e0; /* Lighter text */
--dashboard-surface-color: #121212;
}Custom Theme Provider
Create a theme composable for Vue apps:
// composables/useTheme.ts
import { ref, computed, watch } from 'vue'
const isDarkMode = ref(false)
export function useTheme() {
const themeClass = computed(() =>
isDarkMode.value ? 'dark' : 'light'
)
function toggleTheme() {
isDarkMode.value = !isDarkMode.value
localStorage.setItem('theme', isDarkMode.value ? 'dark' : 'light')
}
function setTheme(theme: 'light' | 'dark') {
isDarkMode.value = theme === 'dark'
localStorage.setItem('theme', theme)
}
// Detect system preference on mount
function detectSystemTheme() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
isDarkMode.value = prefersDark
}
return {
isDarkMode,
themeClass,
toggleTheme,
setTheme,
detectSystemTheme
}
}Usage in Component
<template>
<div :class="themeClass">
<button @click="toggleTheme" class="theme-toggle">
{{ isDarkMode ? '☀️ Light' : '🌙 Dark' }}
</button>
<DocBitsTable :rows="data" :columns="columns" />
</div>
</template>
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme'
const { isDarkMode, themeClass, toggleTheme, detectSystemTheme } = useTheme()
onMounted(() => {
detectSystemTheme()
})
</script>
<style scoped>
div {
background-color: var(--dashboard-surface-color);
color: var(--dashboard-text-color);
transition: background-color 0.3s, color 0.3s;
}
.theme-toggle {
background-color: var(--q-primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.theme-toggle:hover {
opacity: 0.9;
}
</style>Transitions
Smooth color transitions when switching themes:
/* Enable smooth transitions */
body {
transition: background-color 0.3s ease, color 0.3s ease;
}
body * {
transition: background-color 0.3s ease, color 0.3s ease,
border-color 0.3s ease, box-shadow 0.3s ease;
}Accessibility
Dark mode maintains full accessibility in both themes:
Color Contrast
| Combination | Light Mode | Dark Mode | WCAG AA |
|---|---|---|---|
| Primary text | 5.1:1 | 5.3:1 | ✅ Pass |
| Disabled text | 3.2:1 | 3.5:1 | ✅ Pass |
| Interactive | 4.8:1 | 5.2:1 | ✅ Pass |
Focus States
Focus indicators remain visible in both themes:
/* Focus state works in both modes */
button:focus-visible {
outline: 2px solid var(--q-primary);
outline-offset: 2px;
}Reduced Motion
Respect system preference for reduced motion:
@media (prefers-reduced-motion: reduce) {
* {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}Testing Dark Mode
Manual Testing
- Add
class="dark"to<body>element - Verify all components display correctly
- Check color contrast meets WCAG AA
- Test interactive elements (buttons, links, inputs)
- Verify text is readable at all sizes
Unit Testing
import { mount } from '@vue/test-utils'
import Component from './Component.vue'
describe('Dark Mode', () => {
it('should render correctly in dark mode', () => {
const wrapper = mount(Component, {
attachTo: document.body
})
// Add dark class
document.body.classList.add('dark')
// Verify colors use dark mode variables
const element = wrapper.element as HTMLElement
const styles = window.getComputedStyle(element)
expect(styles.backgroundColor).toBe('rgb(26, 26, 26)') // #1a1a1a
})
afterEach(() => {
document.body.classList.remove('dark')
})
})Visual Testing
Use screenshot testing to verify dark mode appearance:
import { test, expect } from '@playwright/test'
test('should match dark mode snapshot', async ({ page }) => {
await page.goto('/components/docbits-table')
// Enable dark mode
await page.evaluate(() => {
document.body.classList.add('dark')
})
// Take screenshot
await expect(page).toHaveScreenshot('docbits-table-dark.png')
})Browser Support
Dark mode is supported in all modern browsers:
| Browser | Support |
|---|---|
| Chrome 76+ | ✅ Full |
| Firefox 67+ | ✅ Full |
| Safari 12.1+ | ✅ Full |
| Edge 79+ | ✅ Full |
Performance
Dark mode has minimal performance impact:
- CSS variable switching: <1ms
- No JavaScript required for theme change
- Transitions are GPU-accelerated
- System preference detection is efficient
Best Practices
1. Use CSS Variables
Always use CSS variables for colors:
/* ✅ Good: Uses CSS variables */
.button {
background-color: var(--q-primary);
color: white;
}
/* ❌ Bad: Hardcoded colors */
.button {
background-color: #2388AE;
color: white;
}2. Provide Escape Hatch
Let users explicitly choose theme:
<script setup lang="ts">
const userThemePreference = ref<'light' | 'dark' | 'auto'>('auto')
watch(userThemePreference, (preference) => {
if (preference === 'auto') {
// Use system preference
} else {
// Use explicit preference
}
})
</script>3. Test Both Modes
Always test your components in both light and dark modes:
# Visual regression testing for both themes
npm run test:visual -- --theme light
npm run test:visual -- --theme dark4. Respect User Preference
Honor user's system preference as default:
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
const initialTheme = prefersDark ? 'dark' : 'light'Related Resources
- Colors - Complete color palette
- Typography - Text styling in dark mode
- Components - Dark mode in components