Theme Customization Guide
Complete Vant theme customization solutions to help you create unique brand styles.
🎨 Theme Customization Solutions
CSS Variable Customization (Recommended)
Vant uses CSS variables to define theme styles. You can customize themes by overriding these variables.
css
/* Global theme variables */
:root {
/* Primary colors */
--van-primary-color: #1989fa;
--van-success-color: #07c160;
--van-warning-color: #ff976a;
--van-danger-color: #ee0a24;
/* Text colors */
--van-text-color: #323233;
--van-text-color-2: #646566;
--van-text-color-3: #969799;
/* Background colors */
--van-background-color: #f7f8fa;
--van-background-color-light: #fafafa;
/* Border colors */
--van-border-color: #ebedf0;
/* Font sizes */
--van-font-size-xs: 10px;
--van-font-size-sm: 12px;
--van-font-size-md: 14px;
--van-font-size-lg: 16px;
--van-font-size-xl: 18px;
/* Spacing */
--van-padding-base: 4px;
--van-padding-xs: 8px;
--van-padding-sm: 12px;
--van-padding-md: 16px;
--van-padding-lg: 24px;
--van-padding-xl: 32px;
/* Border radius */
--van-border-radius-sm: 2px;
--van-border-radius-md: 4px;
--van-border-radius-lg: 8px;
--van-border-radius-max: 999px;
}
Component-level Customization
css
/* Button component customization */
.van-button {
--van-button-primary-background-color: #007bff;
--van-button-primary-border-color: #007bff;
--van-button-border-radius: 8px;
--van-button-font-weight: 600;
}
/* Navigation bar customization */
.van-nav-bar {
--van-nav-bar-background-color: #ffffff;
--van-nav-bar-title-text-color: #323233;
--van-nav-bar-icon-color: #1989fa;
--van-nav-bar-height: 56px;
}
/* Tabs customization */
.van-tabs {
--van-tabs-default-color: #646566;
--van-tabs-line-height: 44px;
--van-tab-active-text-color: #1989fa;
--van-tabs-bottom-bar-color: #1989fa;
}
/* Cell customization */
.van-cell {
--van-cell-background-color: #ffffff;
--van-cell-border-color: #ebedf0;
--van-cell-font-size: 14px;
--van-cell-line-height: 24px;
}
🌈 Brand Color System
Color Configuration
javascript
// theme/colors.js
export const brandColors = {
// Primary colors
primary: {
50: '#e3f2fd',
100: '#bbdefb',
200: '#90caf9',
300: '#64b5f6',
400: '#42a5f5',
500: '#2196f3', // Primary color
600: '#1e88e5',
700: '#1976d2',
800: '#1565c0',
900: '#0d47a1'
},
// Secondary colors
secondary: {
50: '#fce4ec',
100: '#f8bbd9',
200: '#f48fb1',
300: '#f06292',
400: '#ec407a',
500: '#e91e63', // Secondary color
600: '#d81b60',
700: '#c2185b',
800: '#ad1457',
900: '#880e4f'
},
// Functional colors
success: '#4caf50',
warning: '#ff9800',
error: '#f44336',
info: '#2196f3',
// Neutral colors
gray: {
50: '#fafafa',
100: '#f5f5f5',
200: '#eeeeee',
300: '#e0e0e0',
400: '#bdbdbd',
500: '#9e9e9e',
600: '#757575',
700: '#616161',
800: '#424242',
900: '#212121'
}
}
Dynamic Theme Switching
vue
<script setup>
import { ref, watch } from 'vue'
const themes = {
light: {
'--van-primary-color': '#1989fa',
'--van-background-color': '#ffffff',
'--van-text-color': '#323233'
},
dark: {
'--van-primary-color': '#4fc3f7',
'--van-background-color': '#1a1a1a',
'--van-text-color': '#ffffff'
},
custom: {
'--van-primary-color': '#e91e63',
'--van-background-color': '#f5f5f5',
'--van-text-color': '#2c3e50'
}
}
const currentTheme = ref('light')
const applyTheme = (themeName) => {
const theme = themes[themeName]
const root = document.documentElement
Object.keys(theme).forEach(property => {
root.style.setProperty(property, theme[property])
})
// Save theme setting
localStorage.setItem('theme', themeName)
}
watch(currentTheme, (newTheme) => {
applyTheme(newTheme)
}, { immediate: true })
// Restore theme on page load
const savedTheme = localStorage.getItem('theme')
if (savedTheme && themes[savedTheme]) {
currentTheme.value = savedTheme
}
</script>
<template>
<div class="theme-switcher">
<van-cell-group title="Theme Settings">
<van-cell title="Light Theme" clickable @click="currentTheme = 'light'">
<template #right-icon>
<van-icon v-if="currentTheme === 'light'" name="success" color="#1989fa" />
</template>
</van-cell>
<van-cell title="Dark Theme" clickable @click="currentTheme = 'dark'">
<template #right-icon>
<van-icon v-if="currentTheme === 'dark'" name="success" color="#1989fa" />
</template>
</van-cell>
<van-cell title="Custom Theme" clickable @click="currentTheme = 'custom'">
<template #right-icon>
<van-icon v-if="currentTheme === 'custom'" name="success" color="#1989fa" />
</template>
</van-cell>
</van-cell-group>
</div>
</template>
🎭 Dark Mode Support
System-level Dark Mode
css
/* Dark mode variables */
@media (prefers-color-scheme: dark) {
:root {
--van-primary-color: #4fc3f7;
--van-success-color: #4caf50;
--van-warning-color: #ff9800;
--van-danger-color: #f44336;
--van-text-color: #ffffff;
--van-text-color-2: #cccccc;
--van-text-color-3: #999999;
--van-background-color: #1a1a1a;
--van-background-color-light: #2d2d2d;
--van-border-color: #333333;
}
/* Component dark mode adaptation */
.van-nav-bar {
--van-nav-bar-background-color: #2d2d2d;
--van-nav-bar-title-text-color: #ffffff;
}
.van-cell {
--van-cell-background-color: #2d2d2d;
--van-cell-border-color: #333333;
}
.van-button--default {
--van-button-default-background-color: #333333;
--van-button-default-color: #ffffff;
--van-button-default-border-color: #555555;
}
}
Manual Dark Mode Control
javascript
// composables/useTheme.js
import { ref, computed } from 'vue'
const isDark = ref(false)
export function useTheme() {
const toggleDark = () => {
isDark.value = !isDark.value
updateTheme()
}
const updateTheme = () => {
const root = document.documentElement
if (isDark.value) {
root.classList.add('dark')
root.classList.remove('light')
} else {
root.classList.add('light')
root.classList.remove('dark')
}
localStorage.setItem('theme-mode', isDark.value ? 'dark' : 'light')
}
const initTheme = () => {
const savedMode = localStorage.getItem('theme-mode')
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
isDark.value = savedMode ? savedMode === 'dark' : systemDark
updateTheme()
}
const themeClass = computed(() => isDark.value ? 'dark' : 'light')
return {
isDark,
themeClass,
toggleDark,
initTheme
}
}
🖼️ Icon Customization
Custom Icon Library
javascript
// Register custom icons
import { Icon } from 'vant'
// Method 1: Using image URL
Icon.add('custom-icon', 'https://example.com/icon.png')
// Method 2: Using SVG string
Icon.add('custom-svg', `
<svg viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
`)
// Method 3: Using font icons
Icon.add('font-icon', (h) => h('i', { class: 'iconfont icon-custom' }))
Icon Theme Adaptation
vue
<script setup>
import { computed } from 'vue'
import { useTheme } from '@/composables/useTheme'
const { isDark } = useTheme()
const iconColor = computed(() => {
return isDark.value ? '#ffffff' : '#323233'
})
const iconStyle = computed(() => ({
color: iconColor.value,
fontSize: '20px'
}))
</script>
<template>
<div class="icon-container">
<!-- Use computed property to dynamically set icon color -->
<van-icon name="star" :style="iconStyle" />
<!-- Use CSS variables -->
<van-icon name="heart" class="themed-icon" />
<!-- Custom icon -->
<van-icon name="custom-icon" :color="iconColor" />
</div>
</template>
<style scoped>
.themed-icon {
color: var(--van-text-color);
font-size: var(--van-font-size-lg);
}
</style>
📱 Responsive Themes
Breakpoint System
css
/* Responsive breakpoint definitions */
:root {
--breakpoint-xs: 480px;
--breakpoint-sm: 768px;
--breakpoint-md: 1024px;
--breakpoint-lg: 1200px;
--breakpoint-xl: 1440px;
}
/* Small screen adaptation */
@media (max-width: 480px) {
:root {
--van-font-size-md: 12px;
--van-padding-md: 12px;
--van-nav-bar-height: 48px;
}
}
/* Medium screen adaptation */
@media (min-width: 481px) and (max-width: 768px) {
:root {
--van-font-size-md: 14px;
--van-padding-md: 16px;
--van-nav-bar-height: 52px;
}
}
/* Large screen adaptation */
@media (min-width: 769px) {
:root {
--van-font-size-md: 16px;
--van-padding-md: 20px;
--van-nav-bar-height: 56px;
}
}
Dynamic Font Size
javascript
// composables/useResponsive.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useResponsive() {
const screenWidth = ref(window.innerWidth)
const fontSize = ref(14)
const updateFontSize = () => {
screenWidth.value = window.innerWidth
if (screenWidth.value < 480) {
fontSize.value = 12
} else if (screenWidth.value < 768) {
fontSize.value = 14
} else {
fontSize.value = 16
}
document.documentElement.style.setProperty('--dynamic-font-size', `${fontSize.value}px`)
}
onMounted(() => {
updateFontSize()
window.addEventListener('resize', updateFontSize)
})
onUnmounted(() => {
window.removeEventListener('resize', updateFontSize)
})
return {
screenWidth,
fontSize
}
}
🎨 Theme Build Tools
Sass Variable Customization
scss
// theme/variables.scss
$primary-color: #1989fa;
$success-color: #07c160;
$warning-color: #ff976a;
$danger-color: #ee0a24;
$text-color: #323233;
$text-color-2: #646566;
$text-color-3: #969799;
$background-color: #f7f8fa;
$background-color-light: #fafafa;
$border-color: #ebedf0;
$border-width: 1px;
$border-style: solid;
// Fonts
$font-size-xs: 10px;
$font-size-sm: 12px;
$font-size-md: 14px;
$font-size-lg: 16px;
$font-size-xl: 18px;
// Spacing
$padding-base: 4px;
$padding-xs: 8px;
$padding-sm: 12px;
$padding-md: 16px;
$padding-lg: 24px;
$padding-xl: 32px;
// Border radius
$border-radius-sm: 2px;
$border-radius-md: 4px;
$border-radius-lg: 8px;
$border-radius-max: 999px;
// Shadows
$box-shadow-light: 0 1px 3px rgba(0, 0, 0, 0.12);
$box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.12);
$box-shadow-dark: 0 4px 12px rgba(0, 0, 0, 0.15);
// Animation
$animation-duration-base: 0.3s;
$animation-duration-fast: 0.2s;
$animation-timing-function: ease;
Theme Generator
javascript
// utils/themeGenerator.js
export class ThemeGenerator {
constructor(baseTheme = {}) {
this.baseTheme = baseTheme
this.generatedTheme = {}
}
// Generate color variants
generateColorVariants(baseColor) {
const variants = {}
const hsl = this.hexToHsl(baseColor)
// Generate variants with different lightness
for (let i = 1; i <= 9; i++) {
const lightness = Math.max(5, Math.min(95, hsl.l + (5 - i) * 10))
variants[`${i}0`] = this.hslToHex(hsl.h, hsl.s, lightness)
}
return variants
}
// Generate complete theme
generateTheme(primaryColor) {
const primaryVariants = this.generateColorVariants(primaryColor)
return {
primary: primaryVariants,
colors: {
primary: primaryColor,
success: '#07c160',
warning: '#ff976a',
danger: '#ee0a24',
info: primaryVariants['40']
},
text: {
primary: '#323233',
secondary: '#646566',
disabled: '#969799'
},
background: {
primary: '#ffffff',
secondary: '#f7f8fa',
disabled: '#f5f5f5'
},
border: {
color: '#ebedf0',
width: '1px',
style: 'solid'
}
}
}
// Apply theme to CSS variables
applyTheme(theme) {
const root = document.documentElement
Object.entries(theme).forEach(([category, values]) => {
if (typeof values === 'object') {
Object.entries(values).forEach(([key, value]) => {
root.style.setProperty(`--van-${category}-${key}`, value)
})
} else {
root.style.setProperty(`--van-${category}`, values)
}
})
}
// Color conversion utilities
hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255
const g = parseInt(hex.slice(3, 5), 16) / 255
const b = parseInt(hex.slice(5, 7), 16) / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h, s, l = (max + min) / 2
if (max === min) {
h = s = 0
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break
case g: h = (b - r) / d + 2; break
case b: h = (r - g) / d + 4; break
}
h /= 6
}
return { h: h * 360, s: s * 100, l: l * 100 }
}
hslToHex(h, s, l) {
h /= 360
s /= 100
l /= 100
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1/6) return p + (q - p) * 6 * t
if (t < 1/2) return q
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6
return p
}
let r, g, b
if (s === 0) {
r = g = b = l
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
r = hue2rgb(p, q, h + 1/3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1/3)
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16)
return hex.length === 1 ? '0' + hex : hex
}
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}
}
// Usage example
const generator = new ThemeGenerator()
const customTheme = generator.generateTheme('#e91e63')
generator.applyTheme(customTheme)
📚 Best Practices
Theme Design Principles
- Consistency: Maintain unified visual style throughout
- Accessibility: Ensure sufficient contrast ratios
- Responsiveness: Adapt to different screen sizes
- Performance: Avoid excessive style calculations
- Maintainability: Use variables and modular management
Theme Testing Checklist
- [ ] Light/dark mode switching works properly
- [ ] All component styles are consistent
- [ ] Responsive layout adapts well
- [ ] Color contrast meets accessibility standards
- [ ] Theme switching performs well
- [ ] Custom icons display correctly
🔗 Related Resources
- Vant Theme Customization Documentation
- CSS Variables Complete Guide
- Mobile Adaptation Best Practices
- Performance Optimization Complete Guide
Through the above theme customization solutions, you can easily create Vant application interfaces that match your brand style.