Iconography
Vectis UI neither uses nor bundles any icon font. Components rely exclusively on inline SVG paths, sourced directly from the Material Symbols Rounded collection (weight 400, GRAD 0, optical size 24px, under Apache 2.0 license © Google).
Vectis UI integrates 34 icons distributed across distinct modules to ensure perfect tree-shaking: only the icons actually imported or rendered in your application are included in the final bundle. A component like VButton thus embeds no icons by default.
Each icon supports the outline variant and, optionally, the filled version. To optimize code footprint, the filled path is only declared if the FILL property modifies the icon's geometry (which applies to 15 of the 34 icons).
Importing an Icon
Icons provided by the library are exported as JavaScript values from vectis-ui/icons. They are passed directly to dedicated icon props: the exact same icon reference can thus be used interchangeably on the VIcon component, a button, an input field, or a menu item.
import { close, search } from 'vectis-ui/icons'<!-- one of the design system's own icons, imported above --> <VIcon :name="close" /> <!-- every icon prop takes the same value --> <VButton :icon-start="search">Search</VButton> <!-- a bare NAME: your resolver, then the icon font as a ligature --> <VIcon name="favorite" />
Passing a simple string to a prop is equivalent to providing an icon name: the value is then forwarded to your icon resolver or injected as a ligature for an icon font.
Both mechanisms (string or imported object) serve different constraints, and this distinction is what guarantees tree-shaking. An SVG path is only included in the final bundle because a module explicitly imported it, not based on a dynamically evaluated string at runtime.
Using Your Own Icon Library
Using Vectis UI's native icons is entirely optional. By configuring an icon resolver (a function responsible for associating a name with a component or an SVG path), it takes precedence over default icons. All your components thus adopt your own icon set, ensuring perfect visual consistency.
The library includes three helpers (factories) adapted to various market formats. You can also define your own custom resolver via a simple callback function.
Configure the resolver at the global application scale (in main.ts or via a Nuxt plugin), never within a component's setup() nor in a client-only manner. Registering the resolver after the hydration phase or solely on the browser side causes an hydration mismatch, as client-generated icons will no longer match the initial HTML sent by the server.
Regardless of the resolver family, returning undefined indicates the absence of a match for a given name, rather than an instruction to omit rendering. The component then triggers its fallback mechanism: it uses the component's native SVG path, and lastly falls back to the icon font ligature. This makes partial mapping perfectly valid and recommended: you can intercept only the icon names managed by your custom set and delegate the rest to default values.
When an icon name cannot be resolved and no icon font is available, the component displays the raw character string, truncated to the dimensions of the icon container. Although the global layout is preserved, the presence of literal text where a visual symbol should be indicates a resolution failure.
A Class-Driven Font
For CSS class-based icon libraries (such as Font Awesome, Phosphor, or Bootstrap Icons), display relies on injecting a glyph via a pseudo-element. The className function generates the required CSS class from the mapped identifier and variant (filled or outline). The design system applies this class to an internal <span> element, which normalizes the sizing and alignment of all icons using a single CSS rule.
import { classIconResolver, setIconResolver } from 'vectis-ui'
// Font Awesome: the class names the icon, and the family carries the fill.
setIconResolver(
classIconResolver({
aliases: { close: 'xmark', search: 'magnifying-glass', expand_more: 'chevron-down' },
className: (name, filled) => `${filled ? 'fa-solid' : 'fa-regular'} fa-${name}`,
}),
)The strict option, enabled by default, secures the use of partial mapping. Without it, a native identifier provided by a component but absent from your dictionary would generate a nonexistent CSS class for the icon font, resulting in a display glitch (empty rectangle). By refraining from resolving unlisted identifiers, strict mode allows the native SVG path to take over. Your custom identifiers remain handled normally as long as they appear in your resolution table.
A Ligature-Based Font
For ligature-based fonts (such as Material Symbols or an IcoMoon font configured for this purpose), the textual icon identifier corresponds directly to the rendered glyph. This resolver accepts all transmitted names: matching occurs directly at the icon font's ligature table level. This mechanism allows substituting your own font for Vectis UI's native SVG paths across the entire design system.
import { ligatureIconResolver, setIconResolver } from 'vectis-ui'
// The name IS the glyph, so one line covers every icon in the application.
setIconResolver(ligatureIconResolver())
// Or, for a font spelling a few of them differently. The names left out
// are passed on as they stand.
setIconResolver(ligatureIconResolver({ aliases: { close: 'clear', more_horiz: 'more' } }))Since this resolver intercepts all requests, the alias table serves as the sole correspondence layer: a name present in the table is substituted, while an absent name is transmitted unchanged to the icon font. This mechanism bypasses fallback to native SVG paths: any name not managed by the font will result in a missing glyph. Reserve this resolver for projects whose icon font covers all application needs.
A Component Set
For libraries distributing their icons as Vue components (such as Lucide or Untitled UI), rendering relies directly on the imported components. An architectural constraint must be respected: each icon component must possess a single <svg> root node, which is essential for sizing and CSS targeting by the design system. The optional props function allows injecting specific properties during rendering (such as stroke-width or a variant).
import { componentIconResolver, setIconResolver } from 'vectis-ui'
import { Check, Search, X } from 'lucide-vue-next'
setIconResolver(
componentIconResolver({
// Three names answered here; every other one falls back to the built-in drawing.
components: { check: Check, close: X, search: Search },
props: () => ({ strokeWidth: 1.75 }),
}),
)This resolver is strict by design: when an identifier is absent from the correspondence table, no component can be returned. Resolution then falls back to the standard fallback string (native SVG path, then ligature). Mapping only a restricted set of custom icons and delegating the rest to the design system's default values is a common and fully supported use case.
Hand-Written
The three integrated factories do not necessarily cover all use cases. Since a resolver is merely a function associating an icon name with an element to render (component, SVG path, or class), writing your own resolver constitutes a first-class extension model rather than a last-resort solution.
As an example, here is the resolver configured for this documentation site via a universal Nuxt plugin. It handles site UI-specific icons and returns undefined for all other identifiers, triggering the fallback to Vectis UI's native SVG paths.
import { setIconResolver } from 'vectis-ui'
import { docsIcons } from '~/icons/icons'
// A resolver is only a function, so a table is not compulsory. This one is
// the site you are reading: the icons its chrome needs, and `undefined` for
// everything else, which hands the name back to the icon that carries it.
setIconResolver((name, context) => {
const paths = docsIcons[name]
if (!paths) return undefined
return { path: (context.filled && paths[1]) || paths[0] }
})Sizing
By default, an icon adopts a dimension of 1em, automatically inheriting the surrounding text size (font-size). Two mechanisms allow overriding this behavior:
- Local override: The
sizeprop (in pixels), applied directly to the icon, takes precedence over any other styling rule. - Contextual override: The
--vectis-icon-sizeCSS variable, defined on an ancestor element, propagates to all descendant icons that do not specify their ownsizeprop.
It is this second CSS cascade approach that allows control components (via v-control) to automatically adjust child icon sizes according to the parent component's size variant.
<!-- 1em: the icon follows the text around it -->
<p>Ready <VIcon :name="check" /></p>
<!-- one icon, in pixels -->
<VIcon :name="check" :size="32" />
<!-- a context: every icon below is 20px unless it names its own -->
<div class="toolbar"><VIcon :name="check" /></div>
<style>
.toolbar {
--vectis-icon-size: 20px;
}
</style>Resolution Order
The VIcon component evaluates its display source according to an immutable order of priority. This precedence order forms the component's interface contract:
- Prop
render(explicit render function) - Prop
src(imported icon value or object) - Prop
name(sequentially resolved identifier: custom resolver -> embedded native SVG path -> icon font ligature) - Default slot (injected SVG or HTML content)
<!-- an explicit render wins over everything else -->
<VIcon :render="{ src: '/logo.svg' }" label="Logo" />
<!-- an imported icon: your resolver is asked first, its own drawing answers next -->
<VIcon :name="close" />
<!-- a bare name: your resolver, then the ligature font -->
<VIcon name="favorite" />
<!-- nothing named at all: the slot is drawn -->
<VIcon><svg viewBox="0 0 24 24"><path d="…" /></svg></VIcon>A character string is systematically treated as an icon name. To declare an image, a component, or a specific style, the value must be passed explicitly as an object ({ src }, { component }, { path }, { text }, or { class }). The complete absence of heuristics ensures that a namespaced identifier such as mdi:close (Iconify format) reaches your resolver intact without risk of being misinterpretated as a URL or network path.
Existing Icons
Vectis UI embeds a set of 34 native icons. Their identifiers constitute the reference vocabulary for establishing your alias tables. You can directly import the necessary icons from the vectis-ui/icons sub-module for explicit rendering, or map their identifiers within your resolver to substitute default iconography with your own visual system.
When two glyph variants coexist, the filled property switches display from the outline style to the solid style. For icons consisting of a single path, the outline represents the entire motif: the filled property then has no effect on visual rendering.