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