Skip to content

Theme Customization

Overview

Element Plus provides a comprehensive theming system that allows you to customize the appearance of all components to match your brand and design requirements. The theming system is built on CSS custom properties (CSS variables) and SCSS variables, providing flexibility and maintainability.

CSS Variables Approach

Basic Usage

The simplest way to customize Element Plus themes is by overriding CSS variables:

css
:root {
  /* Primary color */
  --el-color-primary: #409eff;
  --el-color-primary-light-3: #79bbff;
  --el-color-primary-light-5: #a0cfff;
  --el-color-primary-light-7: #c6e2ff;
  --el-color-primary-light-8: #d9ecff;
  --el-color-primary-light-9: #ecf5ff;
  --el-color-primary-dark-2: #337ecc;
  
  /* Success color */
  --el-color-success: #67c23a;
  --el-color-success-light-3: #95d475;
  --el-color-success-light-5: #b3e19d;
  --el-color-success-light-7: #d1edc4;
  --el-color-success-light-8: #e1f3d8;
  --el-color-success-light-9: #f0f9eb;
  --el-color-success-dark-2: #529b2e;
  
  /* Warning color */
  --el-color-warning: #e6a23c;
  --el-color-warning-light-3: #eebe77;
  --el-color-warning-light-5: #f3d19e;
  --el-color-warning-light-7: #f8e3c5;
  --el-color-warning-light-8: #faecd8;
  --el-color-warning-light-9: #fdf6ec;
  --el-color-warning-dark-2: #b88230;
  
  /* Danger color */
  --el-color-danger: #f56c6c;
  --el-color-danger-light-3: #f89898;
  --el-color-danger-light-5: #fab6b6;
  --el-color-danger-light-7: #fcd3d3;
  --el-color-danger-light-8: #fde2e2;
  --el-color-danger-light-9: #fef0f0;
  --el-color-danger-dark-2: #c45656;
  
  /* Info color */
  --el-color-info: #909399;
  --el-color-info-light-3: #b1b3b8;
  --el-color-info-light-5: #c8c9cc;
  --el-color-info-light-7: #dedfe0;
  --el-color-info-light-8: #e9e9eb;
  --el-color-info-light-9: #f4f4f5;
  --el-color-info-dark-2: #73767a;
}

Custom Brand Colors

css
/* Custom brand theme */
:root {
  --el-color-primary: #6366f1; /* Indigo */
  --el-color-success: #10b981; /* Emerald */
  --el-color-warning: #f59e0b; /* Amber */
  --el-color-danger: #ef4444;  /* Red */
  --el-color-info: #6b7280;    /* Gray */
}

Typography Customization

css
:root {
  /* Font family */
  --el-font-family: 'Inter', 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
  
  /* Font sizes */
  --el-font-size-extra-large: 20px;
  --el-font-size-large: 18px;
  --el-font-size-medium: 16px;
  --el-font-size-base: 14px;
  --el-font-size-small: 13px;
  --el-font-size-extra-small: 12px;
  
  /* Line heights */
  --el-line-height-primary: 28px;
  --el-line-height-large: 26px;
  --el-line-height-medium: 24px;
  --el-line-height-base: 22px;
  --el-line-height-small: 20px;
  --el-line-height-extra-small: 18px;
}

Spacing and Layout

css
:root {
  /* Border radius */
  --el-border-radius-base: 4px;
  --el-border-radius-small: 2px;
  --el-border-radius-round: 20px;
  --el-border-radius-circle: 100%;
  
  /* Box shadow */
  --el-box-shadow: 0 12px 32px 4px rgba(0, 0, 0, 0.04), 0 8px 20px rgba(0, 0, 0, 0.08);
  --el-box-shadow-light: 0 0 12px rgba(0, 0, 0, 0.12);
  --el-box-shadow-lighter: 0 0 6px rgba(0, 0, 0, 0.12);
  --el-box-shadow-dark: 0 16px 48px 16px rgba(0, 0, 0, 0.08), 0 12px 32px rgba(0, 0, 0, 0.12), 0 8px 16px -8px rgba(0, 0, 0, 0.16);
}

SCSS Variables Approach

Setting up SCSS Customization

  1. Install SCSS dependencies:
bash
npm install sass -D
  1. Create a custom theme file:
scss
// styles/element-variables.scss

// Override default variables
$colors: (
  'primary': (
    'base': #6366f1,
  ),
  'success': (
    'base': #10b981,
  ),
  'warning': (
    'base': #f59e0b,
  ),
  'danger': (
    'base': #ef4444,
  ),
  'error': (
    'base': #ef4444,
  ),
  'info': (
    'base': #6b7280,
  ),
);

// Typography
$font-family: 'Inter', 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;

$font-size: (
  'extra-large': 20px,
  'large': 18px,
  'medium': 16px,
  'base': 14px,
  'small': 13px,
  'extra-small': 12px,
);

// Border radius
$border-radius: (
  'base': 6px,
  'small': 3px,
  'round': 20px,
  'circle': 100%,
);

// Import Element Plus styles
@import 'element-plus/theme-chalk/src/index.scss';
  1. Import in your main CSS file:
scss
// main.scss
@import './styles/element-variables.scss';

Advanced SCSS Customization

scss
// Advanced theme customization

// Component-specific variables
$button: (
  'font-weight': 500,
  'border-width': 1px,
  'border-style': solid,
  'border-color': transparent,
  'border-radius': 6px,
  'box-shadow': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
  'font-size': 14px,
  'padding-vertical': 8px,
  'padding-horizontal': 16px,
);

$input: (
  'font-size': 14px,
  'border-radius': 6px,
  'border-width': 1px,
  'border-style': solid,
  'border-color': #d1d5db,
  'inner-height': 32px,
  'clear-hover-color': #909399,
);

$card: (
  'border-radius': 8px,
  'border-width': 1px,
  'border-style': solid,
  'border-color': #e5e7eb,
  'box-shadow': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
  'padding': 20px,
);

// Import Element Plus
@import 'element-plus/theme-chalk/src/index.scss';

Dark Mode Support

Automatic Dark Mode

css
/* Light mode (default) */
:root {
  --el-bg-color: #ffffff;
  --el-text-color-primary: #303133;
  --el-text-color-regular: #606266;
  --el-text-color-secondary: #909399;
  --el-text-color-placeholder: #a8abb2;
  --el-text-color-disabled: #c0c4cc;
  --el-border-color: #dcdfe6;
  --el-border-color-light: #e4e7ed;
  --el-border-color-lighter: #ebeef5;
  --el-border-color-extra-light: #f2f6fc;
  --el-fill-color: #f0f2f5;
  --el-fill-color-light: #f5f7fa;
  --el-fill-color-lighter: #fafafa;
  --el-fill-color-extra-light: #fafcff;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --el-bg-color: #141414;
    --el-text-color-primary: #e5eaf3;
    --el-text-color-regular: #cfd3dc;
    --el-text-color-secondary: #a3a6ad;
    --el-text-color-placeholder: #8d9095;
    --el-text-color-disabled: #6c6e72;
    --el-border-color: #4c4d4f;
    --el-border-color-light: #414243;
    --el-border-color-lighter: #363637;
    --el-border-color-extra-light: #2b2b2c;
    --el-fill-color: #2b2b2c;
    --el-fill-color-light: #262727;
    --el-fill-color-lighter: #1d1d1d;
    --el-fill-color-extra-light: #191919;
  }
}

Manual Dark Mode Toggle

vue
<template>
  <div :class="{ 'dark': isDark }">
    <el-switch 
      v-model="isDark" 
      @change="toggleDarkMode"
      active-text="Dark"
      inactive-text="Light"
    />
    
    <!-- Your app content -->
    <el-button type="primary">Primary Button</el-button>
    <el-card>
      <p>Card content</p>
    </el-card>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const isDark = ref(false)

const toggleDarkMode = () => {
  document.documentElement.classList.toggle('dark', isDark.value)
  localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}

// Initialize theme from localStorage
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
  isDark.value = savedTheme === 'dark'
  toggleDarkMode()
} else {
  // Use system preference
  isDark.value = window.matchMedia('(prefers-color-scheme: dark)').matches
  toggleDarkMode()
}
</script>

<style>
/* Dark mode styles */
.dark {
  --el-bg-color: #141414;
  --el-text-color-primary: #e5eaf3;
  --el-text-color-regular: #cfd3dc;
  --el-text-color-secondary: #a3a6ad;
  --el-text-color-placeholder: #8d9095;
  --el-text-color-disabled: #6c6e72;
  --el-border-color: #4c4d4f;
  --el-border-color-light: #414243;
  --el-border-color-lighter: #363637;
  --el-border-color-extra-light: #2b2b2c;
  --el-fill-color: #2b2b2c;
  --el-fill-color-light: #262727;
  --el-fill-color-lighter: #1d1d1d;
  --el-fill-color-extra-light: #191919;
}
</style>

Component-Specific Theming

Button Customization

css
/* Custom button styles */
.el-button {
  --el-button-font-weight: 500;
  --el-button-border-width: 1px;
  --el-button-border-radius: 6px;
  --el-button-padding-vertical: 10px;
  --el-button-padding-horizontal: 20px;
}

.el-button--primary {
  --el-button-text-color: #ffffff;
  --el-button-bg-color: var(--el-color-primary);
  --el-button-border-color: var(--el-color-primary);
  --el-button-hover-text-color: #ffffff;
  --el-button-hover-bg-color: var(--el-color-primary-light-3);
  --el-button-hover-border-color: var(--el-color-primary-light-3);
  --el-button-active-text-color: #ffffff;
  --el-button-active-bg-color: var(--el-color-primary-dark-2);
  --el-button-active-border-color: var(--el-color-primary-dark-2);
}

/* Custom button variants */
.el-button--gradient {
  background: linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3));
  border: none;
  color: white;
  transition: all 0.3s ease;
}

.el-button--gradient:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
}

Form Component Theming

css
/* Input customization */
.el-input {
  --el-input-border-radius: 6px;
  --el-input-border-color: #d1d5db;
  --el-input-hover-border-color: var(--el-color-primary);
  --el-input-focus-border-color: var(--el-color-primary);
  --el-input-font-size: 14px;
  --el-input-height: 40px;
}

.el-input__wrapper {
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  transition: all 0.2s ease;
}

.el-input__wrapper:hover {
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
}

.el-input__wrapper.is-focus {
  box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
}

/* Select customization */
.el-select {
  --el-select-border-color-hover: var(--el-color-primary);
  --el-select-input-focus-border-color: var(--el-color-primary);
}

/* Form item customization */
.el-form-item__label {
  font-weight: 500;
  color: var(--el-text-color-primary);
}

Table Theming

css
/* Table customization */
.el-table {
  --el-table-border-color: #e5e7eb;
  --el-table-border: 1px solid var(--el-table-border-color);
  --el-table-text-color: var(--el-text-color-primary);
  --el-table-header-text-color: var(--el-text-color-primary);
  --el-table-header-bg-color: #f9fafb;
  --el-table-row-hover-bg-color: #f3f4f6;
  --el-table-current-row-bg-color: #eff6ff;
  --el-table-header-border-bottom: 2px solid #e5e7eb;
}

.el-table th.el-table__cell {
  font-weight: 600;
  background-color: var(--el-table-header-bg-color);
  border-bottom: var(--el-table-header-border-bottom);
}

.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell {
  background-color: #f9fafb;
}

Advanced Theming Techniques

CSS-in-JS Integration

javascript
// Using CSS-in-JS with Element Plus
const theme = {
  colors: {
    primary: '#6366f1',
    success: '#10b981',
    warning: '#f59e0b',
    danger: '#ef4444',
    info: '#6b7280'
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px'
  },
  borderRadius: {
    sm: '4px',
    md: '6px',
    lg: '8px',
    xl: '12px'
  }
}

// Apply theme to CSS variables
const applyTheme = (theme) => {
  const root = document.documentElement
  
  Object.entries(theme.colors).forEach(([key, value]) => {
    root.style.setProperty(`--el-color-${key}`, value)
  })
  
  Object.entries(theme.spacing).forEach(([key, value]) => {
    root.style.setProperty(`--el-spacing-${key}`, value)
  })
  
  Object.entries(theme.borderRadius).forEach(([key, value]) => {
    root.style.setProperty(`--el-border-radius-${key}`, value)
  })
}

applyTheme(theme)

Dynamic Theme Switching

vue
<template>
  <div class="theme-switcher">
    <el-select v-model="currentTheme" @change="switchTheme">
      <el-option 
        v-for="theme in themes" 
        :key="theme.name"
        :label="theme.label" 
        :value="theme.name"
      />
    </el-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const currentTheme = ref('default')

const themes = [
  {
    name: 'default',
    label: 'Default',
    colors: {
      primary: '#409eff',
      success: '#67c23a',
      warning: '#e6a23c',
      danger: '#f56c6c'
    }
  },
  {
    name: 'purple',
    label: 'Purple',
    colors: {
      primary: '#8b5cf6',
      success: '#10b981',
      warning: '#f59e0b',
      danger: '#ef4444'
    }
  },
  {
    name: 'green',
    label: 'Green',
    colors: {
      primary: '#059669',
      success: '#10b981',
      warning: '#f59e0b',
      danger: '#ef4444'
    }
  }
]

const switchTheme = (themeName) => {
  const theme = themes.find(t => t.name === themeName)
  if (!theme) return
  
  const root = document.documentElement
  
  Object.entries(theme.colors).forEach(([key, value]) => {
    root.style.setProperty(`--el-color-${key}`, value)
  })
  
  // Save to localStorage
  localStorage.setItem('selected-theme', themeName)
}

// Initialize theme from localStorage
const savedTheme = localStorage.getItem('selected-theme')
if (savedTheme) {
  currentTheme.value = savedTheme
  switchTheme(savedTheme)
}
</script>

Theme Generator Utility

typescript
// Theme generator utility
interface ThemeConfig {
  primary: string
  success?: string
  warning?: string
  danger?: string
  info?: string
}

class ThemeGenerator {
  private generateColorPalette(baseColor: string) {
    // Convert hex to HSL
    const hsl = this.hexToHsl(baseColor)
    
    return {
      'base': baseColor,
      'light-3': this.hslToHex(hsl.h, hsl.s, Math.min(hsl.l + 15, 100)),
      'light-5': this.hslToHex(hsl.h, hsl.s, Math.min(hsl.l + 25, 100)),
      'light-7': this.hslToHex(hsl.h, hsl.s, Math.min(hsl.l + 35, 100)),
      'light-8': this.hslToHex(hsl.h, hsl.s, Math.min(hsl.l + 40, 100)),
      'light-9': this.hslToHex(hsl.h, hsl.s, Math.min(hsl.l + 45, 100)),
      'dark-2': this.hslToHex(hsl.h, hsl.s, Math.max(hsl.l - 15, 0))
    }
  }
  
  generateTheme(config: ThemeConfig) {
    const theme: Record<string, any> = {}
    
    // Generate color palettes
    Object.entries(config).forEach(([colorName, colorValue]) => {
      const palette = this.generateColorPalette(colorValue)
      Object.entries(palette).forEach(([shade, value]) => {
        theme[`--el-color-${colorName}${shade === 'base' ? '' : '-' + shade}`] = value
      })
    })
    
    return theme
  }
  
  applyTheme(theme: Record<string, string>) {
    const root = document.documentElement
    Object.entries(theme).forEach(([property, value]) => {
      root.style.setProperty(property, value)
    })
  }
  
  private hexToHsl(hex: string) {
    // Implementation for hex to HSL conversion
    // ... (implementation details)
  }
  
  private hslToHex(h: number, s: number, l: number) {
    // Implementation for HSL to hex conversion
    // ... (implementation details)
  }
}

// Usage
const themeGenerator = new ThemeGenerator()
const customTheme = themeGenerator.generateTheme({
  primary: '#6366f1',
  success: '#10b981',
  warning: '#f59e0b',
  danger: '#ef4444'
})

themeGenerator.applyTheme(customTheme)

Build Integration

Vite Configuration

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `
          @import "./src/styles/element-variables.scss";
        `
      }
    }
  }
})

Webpack Configuration

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          'css-loader',
          {
            loader: 'sass-loader',
            options: {
              additionalData: `@import "./src/styles/element-variables.scss";`
            }
          }
        ]
      }
    ]
  }
}

Best Practices

1. Consistent Color System

  • Use a systematic approach to color generation
  • Maintain proper contrast ratios for accessibility
  • Test colors in both light and dark modes
  • Document your color choices and their usage

2. Scalable Architecture

  • Organize theme files logically
  • Use CSS custom properties for runtime changes
  • Keep SCSS variables for build-time customization
  • Create reusable theme utilities

3. Performance Considerations

  • Minimize CSS custom property usage in animations
  • Use CSS-in-JS sparingly for theme switching
  • Optimize theme bundle size
  • Cache theme preferences

4. Accessibility

  • Ensure sufficient color contrast (WCAG AA: 4.5:1, AAA: 7:1)
  • Test with color blindness simulators
  • Provide high contrast mode options
  • Don't rely solely on color to convey information

5. Maintenance

  • Document theme customization guidelines
  • Create theme testing procedures
  • Version your theme configurations
  • Provide migration guides for theme updates

Conclusion

Element Plus's theming system provides powerful customization capabilities through CSS variables and SCSS. Whether you need simple color changes or comprehensive design system integration, the flexible architecture supports various approaches while maintaining performance and accessibility standards. Choose the method that best fits your project's requirements and team workflow.

Element Plus Study Guide