Skip to content

Overall Architecture and Design Philosophy ​

Overview ​

Element Plus is a Vue 3-based component library that follows modern frontend development principles and design patterns. This document explores the overall architecture and design philosophy that makes Element Plus a robust and scalable UI framework.

Design Philosophy ​

1. Consistency ​

Element Plus maintains visual and behavioral consistency across all components:

  • Visual Consistency: Unified color schemes, typography, spacing, and visual hierarchy
  • Behavioral Consistency: Consistent interaction patterns and user experience
  • API Consistency: Uniform naming conventions and component interfaces

2. Efficiency ​

The library is designed for developer efficiency and performance:

  • Developer Experience: Intuitive APIs and comprehensive documentation
  • Build Efficiency: Tree-shaking support and modular architecture
  • Runtime Performance: Optimized rendering and minimal bundle size

3. Controllability ​

Provides fine-grained control over component behavior:

  • Customization: Extensive theming and styling options
  • Configuration: Flexible component configuration
  • Extension: Easy to extend and customize components

Architectural Principles ​

1. Composition over Inheritance ​

vue
<!-- Good: Composition-based approach -->
<template>
  <el-button :loading="loading" @click="handleClick">
    <el-icon><Plus /></el-icon>
    Add Item
  </el-button>
</template>

<!-- Components are composed together rather than inherited -->

2. Single Responsibility ​

Each component has a clear, single purpose:

vue
<!-- Button component focuses only on button functionality -->
<el-button type="primary" size="large">
  Primary Button
</el-button>

<!-- Icon component focuses only on icon display -->
<el-icon size="20">
  <Edit />
</el-icon>

3. Prop-driven Configuration ​

Components are configured through props rather than global state:

vue
<template>
  <el-table
    :data="tableData"
    :loading="loading"
    :stripe="true"
    :border="true"
    height="400"
  >
    <el-table-column prop="name" label="Name" />
    <el-table-column prop="age" label="Age" />
  </el-table>
</template>

Core Architecture ​

1. Component Layer Structure ​

Element Plus Architecture
├── Theme Layer (CSS Variables, SCSS)
├── Component Layer (Vue Components)
├── Composables Layer (Reusable Logic)
├── Utils Layer (Helper Functions)
└── Types Layer (TypeScript Definitions)

2. Component Anatomy ​

vue
<!-- Typical Element Plus Component Structure -->
<template>
  <div :class="componentClasses" :style="componentStyles">
    <slot name="prefix" />
    <div class="el-component__content">
      <slot />
    </div>
    <slot name="suffix" />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useNamespace } from '@element-plus/hooks'

// Props definition
interface ComponentProps {
  size?: 'large' | 'default' | 'small'
  type?: 'primary' | 'success' | 'warning' | 'danger'
  disabled?: boolean
}

const props = withDefaults(defineProps<ComponentProps>(), {
  size: 'default',
  type: 'primary',
  disabled: false
})

// Namespace for CSS classes
const ns = useNamespace('component')

// Computed properties
const componentClasses = computed(() => [
  ns.b(), // Base class
  ns.m(props.type), // Modifier class
  ns.is('disabled', props.disabled) // State class
])

const componentStyles = computed(() => ({
  // Dynamic styles
}))
</script>

3. Theming Architecture ​

scss
// CSS Variables for theming
:root {
  // Color system
  --el-color-primary: #409eff;
  --el-color-success: #67c23a;
  --el-color-warning: #e6a23c;
  --el-color-danger: #f56c6c;
  
  // Typography
  --el-font-size-base: 14px;
  --el-font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimSun, sans-serif;
  
  // Spacing
  --el-spacing-base: 4px;
  
  // Border radius
  --el-border-radius-base: 4px;
}

// Component-specific variables
.el-button {
  --el-button-font-size: var(--el-font-size-base);
  --el-button-border-radius: var(--el-border-radius-base);
  --el-button-padding-vertical: 8px;
  --el-button-padding-horizontal: 15px;
}

Design Tokens ​

1. Color System ​

typescript
// Color palette structure
interface ColorPalette {
  primary: {
    50: string
    100: string
    200: string
    // ... up to 900
    DEFAULT: string
  }
  success: ColorScale
  warning: ColorScale
  danger: ColorScale
  info: ColorScale
}

// Usage in components
const buttonColors = {
  primary: 'var(--el-color-primary)',
  success: 'var(--el-color-success)',
  warning: 'var(--el-color-warning)',
  danger: 'var(--el-color-danger)'
}

2. Typography Scale ​

scss
// Typography system
$font-sizes: (
  'extra-large': 20px,
  'large': 18px,
  'medium': 16px,
  'base': 14px,
  'small': 13px,
  'extra-small': 12px
);

$line-heights: (
  'extra-large': 28px,
  'large': 26px,
  'medium': 24px,
  'base': 22px,
  'small': 20px,
  'extra-small': 18px
);

3. Spacing System ​

scss
// Spacing scale based on 4px grid
$spacings: (
  'xs': 4px,   // 1 unit
  'sm': 8px,   // 2 units
  'md': 12px,  // 3 units
  'lg': 16px,  // 4 units
  'xl': 20px,  // 5 units
  '2xl': 24px, // 6 units
  '3xl': 32px, // 8 units
  '4xl': 40px  // 10 units
);

Component Communication Patterns ​

1. Props Down, Events Up ​

vue
<!-- Parent Component -->
<template>
  <el-form :model="form" @submit="handleSubmit">
    <el-form-item label="Name">
      <el-input 
        v-model="form.name" 
        @input="handleNameChange"
        @blur="validateName"
      />
    </el-form-item>
  </el-form>
</template>

<script setup>
const form = reactive({ name: '' })

const handleSubmit = (formData) => {
  // Handle form submission
}

const handleNameChange = (value) => {
  // Handle name input change
}

const validateName = () => {
  // Validate name field
}
</script>

2. Provide/Inject for Deep Communication ​

vue
<!-- Form Component (Provider) -->
<script setup>
import { provide } from 'vue'

const formContext = {
  model: formModel,
  rules: formRules,
  validateField: (field) => { /* validation logic */ }
}

provide('elForm', formContext)
</script>

<!-- Form Item Component (Consumer) -->
<script setup>
import { inject } from 'vue'

const formContext = inject('elForm')

const validateCurrentField = () => {
  formContext.validateField(props.prop)
}
</script>

3. Composables for Shared Logic ​

typescript
// useFormValidation composable
export function useFormValidation(rules: FormRules) {
  const errors = ref<Record<string, string>>({})
  const isValid = computed(() => Object.keys(errors.value).length === 0)
  
  const validateField = (field: string, value: any) => {
    const fieldRules = rules[field]
    if (!fieldRules) return true
    
    for (const rule of fieldRules) {
      const result = rule.validator(value)
      if (!result.valid) {
        errors.value[field] = result.message
        return false
      }
    }
    
    delete errors.value[field]
    return true
  }
  
  const validateForm = (formData: Record<string, any>) => {
    let valid = true
    for (const field in formData) {
      if (!validateField(field, formData[field])) {
        valid = false
      }
    }
    return valid
  }
  
  return {
    errors: readonly(errors),
    isValid,
    validateField,
    validateForm
  }
}

Performance Considerations ​

1. Tree Shaking Support ​

typescript
// Individual component imports
import { ElButton } from 'element-plus'
import { ElInput } from 'element-plus'

// Or specific imports
import ElButton from 'element-plus/es/components/button'
import ElInput from 'element-plus/es/components/input'

2. Lazy Loading ​

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

// Lazy load heavy components
const ElTable = defineAsyncComponent(() => 
  import('element-plus/es/components/table')
)

const ElDatePicker = defineAsyncComponent(() => 
  import('element-plus/es/components/date-picker')
)
</script>

3. Virtual Scrolling ​

vue
<!-- For large datasets -->
<template>
  <el-table-v2
    :columns="columns"
    :data="data"
    :width="700"
    :height="400"
    fixed
  />
</template>

Accessibility Architecture ​

1. ARIA Support ​

vue
<template>
  <button
    :class="buttonClasses"
    :aria-disabled="disabled"
    :aria-pressed="pressed"
    :aria-label="ariaLabel"
    role="button"
  >
    <slot />
  </button>
</template>

2. Keyboard Navigation ​

typescript
// Keyboard navigation composable
export function useKeyboardNavigation(items: Ref<any[]>) {
  const activeIndex = ref(0)
  
  const handleKeydown = (event: KeyboardEvent) => {
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault()
        activeIndex.value = Math.min(activeIndex.value + 1, items.value.length - 1)
        break
      case 'ArrowUp':
        event.preventDefault()
        activeIndex.value = Math.max(activeIndex.value - 1, 0)
        break
      case 'Enter':
      case ' ':
        event.preventDefault()
        // Activate current item
        break
    }
  }
  
  return {
    activeIndex,
    handleKeydown
  }
}

3. Focus Management ​

typescript
// Focus trap for modals
export function useFocusTrap(containerRef: Ref<HTMLElement | null>) {
  const focusableElements = computed(() => {
    if (!containerRef.value) return []
    
    return Array.from(
      containerRef.value.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      )
    ) as HTMLElement[]
  })
  
  const trapFocus = (event: KeyboardEvent) => {
    if (event.key !== 'Tab') return
    
    const elements = focusableElements.value
    if (elements.length === 0) return
    
    const firstElement = elements[0]
    const lastElement = elements[elements.length - 1]
    
    if (event.shiftKey) {
      if (document.activeElement === firstElement) {
        event.preventDefault()
        lastElement.focus()
      }
    } else {
      if (document.activeElement === lastElement) {
        event.preventDefault()
        firstElement.focus()
      }
    }
  }
  
  return { trapFocus }
}

Testing Architecture ​

1. Unit Testing ​

typescript
// Component unit test
import { mount } from '@vue/test-utils'
import { ElButton } from 'element-plus'

describe('ElButton', () => {
  it('renders correctly', () => {
    const wrapper = mount(ElButton, {
      props: {
        type: 'primary'
      },
      slots: {
        default: 'Click me'
      }
    })
    
    expect(wrapper.classes()).toContain('el-button--primary')
    expect(wrapper.text()).toBe('Click me')
  })
  
  it('emits click event', async () => {
    const wrapper = mount(ElButton)
    
    await wrapper.trigger('click')
    
    expect(wrapper.emitted('click')).toHaveLength(1)
  })
})

2. Integration Testing ​

typescript
// Form integration test
import { mount } from '@vue/test-utils'
import { ElForm, ElFormItem, ElInput, ElButton } from 'element-plus'

describe('Form Integration', () => {
  it('validates form correctly', async () => {
    const wrapper = mount({
      template: `
        <el-form ref="form" :model="form" :rules="rules">
          <el-form-item prop="name">
            <el-input v-model="form.name" />
          </el-form-item>
          <el-button @click="submit">Submit</el-button>
        </el-form>
      `,
      data() {
        return {
          form: { name: '' },
          rules: {
            name: [{ required: true, message: 'Name is required' }]
          }
        }
      },
      methods: {
        async submit() {
          await this.$refs.form.validate()
        }
      }
    }, {
      global: {
        components: { ElForm, ElFormItem, ElInput, ElButton }
      }
    })
    
    await wrapper.find('button').trigger('click')
    
    expect(wrapper.find('.el-form-item__error').exists()).toBe(true)
  })
})

Build and Distribution ​

1. Module Formats ​

javascript
// ESM (ES Modules)
import { ElButton } from 'element-plus'

// CommonJS
const { ElButton } = require('element-plus')

// UMD (Browser)
// <script src="element-plus/dist/index.full.js"></script>
// const { ElButton } = ElementPlus

2. CSS Distribution ​

scss
// Full CSS bundle
@import 'element-plus/dist/index.css';

// Individual component CSS
@import 'element-plus/es/components/button/style/css';

// SCSS source
@import 'element-plus/theme-chalk/src/index.scss';

Best Practices ​

1. Component Design ​

  • Keep components focused and single-purpose
  • Use composition over inheritance
  • Provide sensible defaults
  • Make components configurable through props
  • Follow consistent naming conventions

2. Performance ​

  • Implement tree-shaking support
  • Use lazy loading for heavy components
  • Optimize bundle size
  • Implement virtual scrolling for large datasets

3. Accessibility ​

  • Include ARIA attributes
  • Support keyboard navigation
  • Provide focus management
  • Ensure color contrast compliance
  • Test with screen readers

4. Maintainability ​

  • Write comprehensive tests
  • Document component APIs
  • Use TypeScript for type safety
  • Follow consistent code style
  • Implement proper error handling

Conclusion ​

Element Plus's architecture is built on solid principles of consistency, efficiency, and controllability. The modular design, comprehensive theming system, and focus on developer experience make it a robust choice for Vue 3 applications. Understanding these architectural principles helps developers make the most of the library and contribute to its ecosystem effectively.

Element Plus Study Guide