Implementing an Icon Component in Vue3: Structure, Props, CSS Variables, and Global Registration
This article walks through building a reusable Icon component with Vue3's Composition API, covering directory layout, prop definitions, TypeScript typing, CSS variable styling, local and global registration, type augmentation, and the use of the defineOptions macro for component naming.
In this tutorial we implement a simple Icon component to demonstrate the complete workflow of a Vue3 component library using the Composition API, TypeScript, and CSS variables.
Component directory structure
├── packages
│ ├── components
│ │ ├── icon
│ │ │ ├── __tests__ # test directory
│ │ │ ├── src # component source
│ │ │ │ ├── icon.ts # props and TS types
│ │ │ │ └── icon.vue # component template
│ │ │ ├── style # component styles
│ │ │ └── index.ts # component entry
│ │ └── package.jsonUsing this layout showcases the advantages of Vue3's Composition API for high cohesion and low coupling, as the component logic, types, and template are separated.
Defining component props
We declare two props, color and size , with appropriate TypeScript types and validation. The props are defined in packages/components/icon/src/icon.ts :
export const iconProps = {
color: String,
size: [Number, String] // size can be a number or a string
}Vue's ExtractPropTypes and PropType are used to infer exact prop types, converting constructor types (e.g., StringConstructor ) to primitive types ( string ).
Single‑direction data flow
Props are passed from parent to child only; the child cannot modify the parent’s state. Therefore we mark the props object as as const to make it read‑only for stricter type inference.
export const iconProps = {
color: String,
size: [Number, String]
} as constScript setup SFC
The component template is minimal – an i element that receives a slot. In icon.vue we use defineProps to expose the props to the template:
import { iconProps } from './icon'
const props = defineProps(iconProps)We compute a style object that applies fontSize and a CSS custom property --color based on the received props:
const style = computed
(() => {
if (!props.size && !props.color) return {}
return {
fontSize: isUndefined(props.size) ? undefined : addUnit(props.size),
'--color': props.color
}
})The template binds this object with :style="style" so the icon’s size and color can be controlled via props.
CSS variables in Vue
We declare custom properties with a double hyphen prefix (e.g., --color ) to avoid conflicts with Sass ( $ ) and Less ( @ ). The component’s SCSS uses the variable:
.el-icon {
color: var(--color);
fill: currentColor;
}When the component is used, the parent can set color and size props, which flow through the computed style to the CSS variable, affecting both font‑based and SVG icons.
Component registration
Two registration methods are demonstrated:
Local registration in App.vue using import ElIcon from '@cobyte-ui/components/icon' and adding it to the components option.
Global registration via a plugin that iterates over an array of components and calls app.use(component) . The withInstall helper adds an install method to each component.
export const withInstall =
(comp: T) => {
(comp as SFCWithInstall
).install = (app) => {
const { name } = comp as unknown as { name: string }
app.component(name, comp as SFCWithInstall
)
}
return comp as SFCWithInstall
}After installing the plugin, ElIcon can be used anywhere without explicit import.
Global component type augmentation
Vue3 does not provide TypeScript typings for globally registered components. We create a components.d.ts file that augments the @vue/runtime-core module:
import type Icon from '@cobyte-ui/components/icon'
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
ElIcon: typeof Icon
}
}
export {}This gives IDEs proper type hints for ElIcon in templates.
Setting component name with script‑setup
Because script setup does not expose a name option, we use the unplugin-vue-define-options macro defineOptions to declare the component name directly inside the setup block:
defineOptions({
name: 'ElIcon'
})After installing the plugin (via Vite, Webpack, etc.) the component name is correctly registered, enabling dynamic naming in the global plugin.
Icon content types
Two ways to provide the actual icon graphics are covered:
SVG component icons : each SVG is wrapped in a Vue SFC, with fill="currentColor" so it inherits the CSS color variable.
Font icons : a custom @font-face is defined and icons are rendered via <i class="iconfont iconlogistics-car"></i> , also respecting the CSS variable for color.
Both approaches are demonstrated with example code and screenshots.
Summary
The tutorial shows how a seemingly simple Icon component encapsulates the full lifecycle of a Vue3 component: directory organization, prop definition and validation, one‑way data flow, CSS variable styling, local and global registration, TypeScript type augmentation, and naming via defineOptions . It also explains how to embed SVG or font‑based icons, making the component ready for use in a larger UI library such as Element Plus.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.