How to Create Custom Components That Integrate with Element UI Patterns
Extend the ElementUIComponent base class from 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. This class extends Vue and provides a static install method required for Vue.use() registration, along with common type definitions for sizing and alignment.
// 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, 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.
// 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.
// packages/button/src/button.vue (lines 26-30)
export default {
name: 'ElButton',
componentName: 'ElButton',
// ...
}
The 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.
<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: ElementUIComponentpulls in the staticinstallmethod and shared type definitionsname: 'ElMyCard'ensures registration as<el-my-card>following the library's kebab-case conventioncomponentNameenables participation in theemitter.jsevent 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):
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):
// 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:
<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
ElementUIComponentto ensure your component exports the required staticinstallmethod and TypeScript definitions. -
Use the
Elprefix in yournameproperty (e.g.,ElMyCard) to maintain consistency with the library's registration pattern and avoid naming collisions. -
Implement the size resolution pattern by checking
this.sizefirst, then falling back to(this.$ELEMENT || {}).size, ensuring your component respects global configuration changes. -
Define
componentNameif your component needs to communicate with parent or child components using thedispatchandbroadcastmethods fromsrc/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 likeel-cardandel-dialog. -
Add to the global install array in
src/index.jsif you need your component auto-registered when users callVue.use(ElementUI).
Summary
- Extend
ElementUIComponentfromtypes/component.d.tsto inherit the staticinstallmethod and shared type definitions required for global registration. - Follow the
El*naming convention and define bothnameandcomponentNameproperties to ensure proper tag registration and event propagation compatibility. - Respect global configuration by reading
this.$ELEMENTfor default values likesize, following the precedence pattern used inpackages/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.jswhen your component needs to communicate with ancestors or descendants without direct prop drilling. - Register via
Vue.use()for individual projects, or modifysrc/index.jsto 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 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.
Can custom components use Element UI's internal event communication system?
Yes. By importing the mixin from 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →