Skip to content

Theme Customization Guide

Complete Vant theme customization solutions to help you create unique brand styles.

🎨 Theme Customization Solutions

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

  1. Consistency: Maintain unified visual style throughout
  2. Accessibility: Ensure sufficient contrast ratios
  3. Responsiveness: Adapt to different screen sizes
  4. Performance: Avoid excessive style calculations
  5. 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

Through the above theme customization solutions, you can easily create Vant application interfaces that match your brand style.

Enterprise-level mobile solution based on Vant