Frontend Development 25 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing an Icon Component in Vue3: Structure, Props, CSS Variables, and Global Registration

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.json

Using 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 const

Script 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.

typescriptComposition APIVue3CSS variablesComponent RegistrationIcon Component
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.