# How to Create Custom Components That Integrate with Element UI Patterns

> Learn to create custom Vue components that integrate seamlessly with Element UI patterns. Extend ElementUIComponent, implement install, and use BEM for consistent design and functionality.

- Repository: [饿了么前端/element](https://github.com/ElemeFE/element)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Extend the `ElementUIComponent` base class from [`types/component.d.ts`](https://github.com/ElemeFE/element/blob/main/types/component.d.ts), implement the static `install` method, and follow BEM naming conventions to build custom Vue components that seamlessly inherit Element UI's global configuration, event system, and visual design language.**

Element UI provides a lightweight, extensible framework for building consistent Vue.js interfaces. By mirroring the internal architecture found in the ElemeFE/element repository, you can create custom components that respect global `$ELEMENT` settings, participate in the library's event propagation system, and maintain visual parity with native elements. This guide demonstrates the exact patterns used in the source code to ensure your custom UI elements behave as first-class citizens of the Element UI ecosystem.

## Core Architecture of Element UI Components

### The ElementUIComponent Base Class

Every native Element UI component derives from a shared base class defined in [`types/component.d.ts`](https://github.com/ElemeFE/element/blob/main/types/component.d.ts). This class extends Vue and provides a static `install` method required for `Vue.use()` registration, along with common type definitions for sizing and alignment.

```typescript
// types/component.d.ts
export declare class ElementUIComponent extends Vue {
  static install(vue: typeof Vue): void
}

export type ElementUIComponentSize = 'large' | 'medium' | 'small' | 'mini'
export type ElementUIHorizontalAlignment = 'left' | 'center' | 'right'

```

By extending this class, your custom component automatically gains the ability to register globally via `Vue.use()` without writing boilerplate installation logic.

### Global Installation and Configuration

The central plugin logic lives in [`src/index.js`](https://github.com/ElemeFE/element/blob/main/src/index.js), where the library iterates over a `components` array and registers each one with `Vue.component()`. This file also establishes the global `$ELEMENT` configuration object that components reference for default props like `size`.

```javascript
// src/index.js (lines 83-100)
const install = function(Vue, opts = {}) {
  components.forEach(component => {
    Vue.component(component.name, component)
  })
  Vue.use(InfiniteScroll)
  Vue.use(Loading.directive)
  Vue.prototype.$ELEMENT = { size: opts.size || '', zIndex: opts.zIndex || 2000 }
}

```

To bundle your custom component with the default Element UI installation, you must add it to this `components` array and expose a `name` property matching the desired tag name (e.g., `'ElMyCard'` registers as `<el-my-card>`).

### Component Naming and Event Communication

Native components define both `name` and `componentName` properties. The `name` property controls the global tag registration, while `componentName` enables the `dispatch` and `broadcast` event propagation system found in [`src/mixins/emitter.js`](https://github.com/ElemeFE/element/blob/main/src/mixins/emitter.js).

```javascript
// packages/button/src/button.vue (lines 26-30)
export default {
  name: 'ElButton',
  componentName: 'ElButton',
  // ...
}

```

The [`emitter.js`](https://github.com/ElemeFE/element/blob/main/emitter.js) mixin provides methods for communicating up and down the component tree without tight coupling, which is essential for complex components like form inputs or nested menus.

## Step-by-Step Implementation Guide

### 1. Create the Component Skeleton

Create a single-file component that extends `ElementUIComponent`, respects the global size configuration, and uses BEM-style CSS classes. This example implements a `MyCard` component following the exact patterns from [`packages/button/src/button.vue`](https://github.com/ElemeFE/element/blob/main/packages/button/src/button.vue).

```vue
<template>
  <div :class="cardClass">
    <slot name="header"></slot>
    <div class="my-card__body"><slot></slot></div>
    <slot name="footer"></slot>
  </div>
</template>

<script>
import { ElementUIComponent } from 'element-ui/src/types/component'

export default {
  name: 'ElMyCard',
  componentName: 'ElMyCard',
  extends: ElementUIComponent,
  props: {
    size: {
      type: String,
      validator: val => ['large', 'medium', 'small', 'mini'].includes(val)
    }
  },
  computed: {
    cardSize() {
      return this.size || (this.$ELEMENT || {}).size || ''
    },
    cardClass() {
      return [
        'el-my-card',
        this.cardSize ? `el-my-card--${this.cardSize}` : ''
      ]
    }
  }
}
</script>

<style scoped>
.el-my-card { border: 1px solid #ebeef5; border-radius: 4px; padding: 20px; }
.el-my-card--large   { font-size: 16px; }
.el-my-card--medium  { font-size: 14px; }
.el-my-card--small   { font-size: 12px; }
.el-my-card--mini    { font-size: 10px; }
</style>

```

**Key implementation details:**
- **`extends: ElementUIComponent`** pulls in the static `install` method and shared type definitions
- **`name: 'ElMyCard'`** ensures registration as `<el-my-card>` following the library's kebab-case convention
- **`componentName`** enables participation in the [`emitter.js`](https://github.com/ElemeFE/element/blob/main/emitter.js) event system if you import the mixin
- **Size resolution** follows the pattern `local prop > $ELEMENT.size > default`, matching the logic in native components

### 2. Register Your Component

You have two registration options depending on whether you are extending the library or using the component in a single project.

**Individual registration** (recommended for project-specific components):

```javascript
import Vue from 'vue'
import MyCard from '@/components/MyCard.vue'

// Uses the inherited static install method
Vue.use(MyCard)

// Or manual registration:
Vue.component(MyCard.name, MyCard)

```

**Bundling with Element UI** (to include in the default plugin):

```javascript
// src/index.js
import MyCard from '../packages/my-card/index.js'

const components = [
  // ... existing components
  MyCard
]

// Rebuild the library to include your component in Vue.use(ElementUI)

```

### 3. Usage in Templates

Once registered, the component integrates seamlessly with other Element UI elements and respects global configuration:

```html
<template>
  <el-my-card size="large">
    <template #header>
      <h3>Custom Card Title</h3>
    </template>
    <p>This body content respects the global theme.</p>
    <template #footer>
      <el-button type="primary" @click="handleAction">Confirm</el-button>
    </template>
  </el-my-card>
</template>

<script>
export default {
  methods: {
    handleAction() {
      this.$message({ type: 'success', message: 'Custom component works!' })
    }
  }
}
</script>

```

The component automatically inherits the `size` value from `Vue.use(ElementUI, { size: 'medium' })` if no local `size` prop is specified, maintaining consistency across your application.

## Best Practices for Seamless Integration

- **Extend `ElementUIComponent`** to ensure your component exports the required static `install` method and TypeScript definitions.

- **Use the `El` prefix** in your `name` property (e.g., `ElMyCard`) to maintain consistency with the library's registration pattern and avoid naming collisions.

- **Implement the size resolution pattern** by checking `this.size` first, then falling back to `(this.$ELEMENT || {}).size`, ensuring your component respects global configuration changes.

- **Define `componentName`** if your component needs to communicate with parent or child components using the `dispatch` and `broadcast` methods from [`src/mixins/emitter.js`](https://github.com/ElemeFE/element/blob/main/src/mixins/emitter.js).

- **Follow BEM CSS conventions** with the `el-` prefix (e.g., `el-my-card`, `el-my-card--large`) so your styles integrate with the existing theme-chalk architecture and modifier utilities.

- **Provide flexible slots** (`header`, `default`, `footer`) to match the compositional API pattern used by native components like `el-card` and `el-dialog`.

- **Add to the global install array** in [`src/index.js`](https://github.com/ElemeFE/element/blob/main/src/index.js) if you need your component auto-registered when users call `Vue.use(ElementUI)`.

## Summary

- **Extend `ElementUIComponent`** from [`types/component.d.ts`](https://github.com/ElemeFE/element/blob/main/types/component.d.ts) to inherit the static `install` method and shared type definitions required for global registration.
- **Follow the `El*` naming convention** and define both `name` and `componentName` properties to ensure proper tag registration and event propagation compatibility.
- **Respect global configuration** by reading `this.$ELEMENT` for default values like `size`, following the precedence pattern used in [`packages/button/src/button.vue`](https://github.com/ElemeFE/element/blob/main/packages/button/src/button.vue).
- **Use BEM CSS naming** with the `el-` prefix to maintain visual consistency with the theme-chalk system and support size modifiers.
- **Leverage [`src/mixins/emitter.js`](https://github.com/ElemeFE/element/blob/main/src/mixins/emitter.js)** when your component needs to communicate with ancestors or descendants without direct prop drilling.
- **Register via `Vue.use()`** for individual projects, or modify [`src/index.js`](https://github.com/ElemeFE/element/blob/main/src/index.js) to bundle your component with the core library distribution.

## Frequently Asked Questions

### Do I need to modify Element UI source code to add custom components?

No. For project-specific components, you can register them individually using `Vue.use(MyComponent)` without touching the library source. However, to include your component in the default `Vue.use(ElementUI)` bundle—so it registers automatically alongside native components—you must add the import and component object to the `components` array in [`src/index.js`](https://github.com/ElemeFE/element/blob/main/src/index.js) and rebuild the library.

### How does the size prop interact with global Element UI configuration?

Components should implement a computed property that checks the local `size` prop first, then falls back to `this.$ELEMENT.size` (set during `Vue.use(ElementUI, { size: 'medium' })`), and finally defaults to an empty string. This pattern ensures your component respects the global size setting while allowing local overrides, exactly as implemented in [`packages/button/src/button.vue`](https://github.com/ElemeFE/element/blob/main/packages/button/src/button.vue).

### Can custom components use Element UI's internal event communication system?

Yes. By importing the mixin from [`src/mixins/emitter.js`](https://github.com/ElemeFE/element/blob/main/src/mixins/emitter.js), your component gains access to `dispatch(componentName, eventName, payload)` for emitting events to ancestors and `broadcast(componentName, eventName, payload)` for descendants. This is essential for building complex components like custom form inputs that need to notify parent forms of validation changes without explicit prop binding.

### What CSS naming convention should custom components follow to match Element UI's theme?

Use BEM (Block Element Modifier) methodology with the `el-` namespace prefix. Define your block as `el-<component-name>` (e.g., `el-my-card`), elements as `el-my-card__body`, and modifiers as `el-my-card--<modifier>` (e.g., `el-my-card--large`). This ensures compatibility with the theme-chalk system and allows users to override styles using the same SCSS variables and mixins as native components.