Icons in Vue 3
Every glyphfox icon is a Vue 3 component with the same props as every other glyphfox package. This guide covers the install, props in templates, icons by name, morphing, global registration, TypeScript and the mistakes that leave an icon empty.
Install
npm install glyphfox
glyphfox has no dependencies. Vue 3.2 or newer is an optional peer dependency, only needed when you import glyphfox/vue. The same package also holds the React, Svelte and web component builds, so there is nothing else to install.
Each icon is a component named after the icon in PascalCase with an Icon suffix: arrow-right becomes ArrowRightIcon, x becomes XIcon. The icon browser shows the name of every icon and copies the Vue snippet for you.
Use the components
<script setup> import { HomeIcon, SearchIcon, BellIcon } from 'glyphfox/vue'; defineProps({ unread: { type: Number, default: 0 } }); </script> <template> <nav class="toolbar"> <a href="/" aria-label="Home"><HomeIcon /></a> <SearchIcon :size="20" :weight="1.5" class="muted" /> <BellIcon variant="duotone" secondary-color="#ff7a1a" :modifier="unread ? 'dot' : undefined" /> </nav> </template>
Props are declared in camelCase, and Vue accepts both spellings in templates: secondary-color and secondaryColor reach the same prop. Bind numbers with :size="20"; a plain size="20" also works, because size, weight, rotate, duration and the other numeric props accept strings.
The components do not declare class, style or listeners, so Vue passes them through to the root <svg>. Your classes are merged with the built in glyphfox glyphfox-NAME classes, and @click, aria-*, data-* and id land on the svg itself.
Strokes use currentColor, so an icon takes the CSS color of its parent and follows hover states and themes for free. The color prop sets the color on the svg when you need a fixed one. The svg gets width and height attributes from size (24 by default); CSS width and height on a class override them.
Weight, corners and style
Three props change the drawing itself rather than the box around it: weight sets the stroke from 1 to 3, corners switches between round and sharp joins, and variant picks outline, duotone or solid. Below 24 px the stroke is thickened slightly so small icons do not look faint; absolute-weight keeps the exact stroke you asked for.
<script setup> import { FolderIcon } from 'glyphfox/vue'; </script> <template> <FolderIcon :weight="1" /> <FolderIcon :weight="3" corners="sharp" /> <FolderIcon variant="duotone" secondary-color="#ff7a1a" /> <FolderIcon variant="solid" /> <FolderIcon :size="16" :weight="1.5" absolute-weight /> </template>
The solid style is generated at build time from the closed shapes of each icon, with inner lines cut out as negative space. Icons without closed shapes keep their outline when you ask for solid.
Icons by name, modifiers and fusion
Icon renders any spec of the name syntax: a plain name, a modifier badge (file+check, with @tl, @tr, @bl or @br for the corner), a fusion (cloud&lock:overlap) or a style (heart/solid). Fusion takes a list of names and a layout. Both are useful when the icon comes from data, such as a menu definition, a status field or a CMS.
Components such as FileIcon bring their own data. A name inside a string has no import behind it, so register the data once with registerIcons. The registry is shared by Icon, Fusion, MorphIcon and every morph-to prop, so one module covers the whole app. Modifier badges are drawn by the engine and need no registration.
import { registerIcons } from 'glyphfox/vue'; import file from 'glyphfox/icons/file'; import cloud from 'glyphfox/icons/cloud'; import lock from 'glyphfox/icons/lock'; import menu from 'glyphfox/icons/menu'; import x from 'glyphfox/icons/x'; import pause from 'glyphfox/icons/pause'; // Every name used in a string: Icon, Fusion, MorphIcon and morph-to. registerIcons([file, cloud, lock, menu, x, pause]);
import { createApp } from 'vue'; import App from './App.vue'; import './icons.js'; createApp(App).mount('#app');
<script setup> import { Icon, Fusion } from 'glyphfox/vue'; defineProps({ saved: Boolean }); </script> <template> <Icon :name="saved ? 'file+check' : 'file'" /> <Icon name="file+alert@tr" variant="duotone" /> <Fusion :icons="['cloud', 'lock']" layout="overlap" :gap="1" /> </template>
A fusion is a real icon: it follows weight, variant and every other prop, and where the parts overlap the part behind is cut around the part in front. Layouts are overlap, side, stack, grid, orbit, badge and inside; scale and offsets fine tune the parts.
Morph between icons
There are three ways to morph. MorphIcon animates whenever its name changes. Any icon component takes morph-to with a trigger (mount, hover, click or state). And the useMorph composable returns the animated frames for your own rendering.
<script setup> import { ref } from 'vue'; import { MorphIcon } from 'glyphfox/vue'; const open = ref(false); </script> <template> <button type="button" aria-label="Menu" :aria-expanded="open" @click="open = !open"> <MorphIcon :name="open ? 'x' : 'menu'" :duration="300" /> </button> </template>
<script setup> import { HeartIcon, PlayIcon } from 'glyphfox/vue'; defineProps({ playing: Boolean }); </script> <template> <!-- toggles on every click --> <HeartIcon morph-to="heart/solid" trigger="click" /> <!-- shows pause while playing is true --> <PlayIcon morph-to="pause" trigger="state" :active="playing" /> </template>
useMorph accepts a ref, a getter or a plain value and returns a computed { icon, variant } frame that animates whenever the spec changes. Pass the frame to Icon through its icon prop, or hand it to your own renderer.
<script setup> import { ref } from 'vue'; import { Icon, useMorph } from 'glyphfox/vue'; const playing = ref(false); const frame = useMorph(() => (playing.value ? 'pause' : 'play'), { duration: 250 }); </script> <template> <button type="button" aria-label="Play" :aria-pressed="playing" @click="playing = !playing"> <Icon v-if="frame" :icon="frame.icon" :variant="frame.variant" /> </button> </template>
Hand tuned pairs make common transitions feel designed: menu to x, play to pause, plus to check, sun to moon, eye to eye-off, bell to bell-ring, heart to heart/solid, copy to check, lock to unlock and wifi to wifi-off. Any other pair still morphs with an automatic match. The animated icons guide covers durations, delays and reduced motion.
Living states
The animate prop adds a preset that understands the icon: draw traces the strokes in drawing order, shake swings from the top, and pop, wiggle, spin, pulse and bounce do what they say. The animation is a small stylesheet inside the svg, so it needs no CSS import and it stops for visitors who prefer reduced motion.
<script setup> import { BellIcon, LoaderCircleIcon, SparklesIcon } from 'glyphfox/vue'; defineProps({ hasNews: Boolean, loading: Boolean }); </script> <template> <BellIcon animate="shake" trigger="state" :active="hasNews" /> <LoaderCircleIcon v-if="loading" animate="spin" /> <SparklesIcon animate="draw" trigger="hover" /> </template>
Global registration
Importing icons where you use them keeps each bundle small. When a design system needs a fixed set in every template, register those components once with app.component. Give Icon a distinct name such as GfIcon, so it cannot collide with another global component.
import { createApp } from 'vue'; import { HomeIcon, SearchIcon, SettingsIcon, Icon } from 'glyphfox/vue'; import App from './App.vue'; import './icons.js'; const app = createApp(App); app.component('HomeIcon', HomeIcon); app.component('SearchIcon', SearchIcon); app.component('SettingsIcon', SettingsIcon); app.component('GfIcon', Icon); app.mount('#app');
With TypeScript, declare the global components so templates are checked:
import type { HomeIcon, SearchIcon, SettingsIcon, Icon } from 'glyphfox/vue'; declare module 'vue' { interface GlobalComponents { HomeIcon: typeof HomeIcon; SearchIcon: typeof SearchIcon; SettingsIcon: typeof SettingsIcon; GfIcon: typeof Icon; } } export {};
TypeScript
Every component is typed as GlyphComponent with the shared props, so editors complete variant, modifier, animate and the rest. Icon specs are plain strings in the Vue types, because Vue prop inference cannot expand the full union of icon names. When you keep names in your own code, type them with IconName from glyphfox to catch typos at compile time.
import type { IconName } from 'glyphfox'; export const nav: { label: string; to: string; icon: IconName }[] = [ { label: 'Home', to: '/', icon: 'home' }, { label: 'Search', to: '/search', icon: 'search' }, { label: 'Settings', to: '/settings', icon: 'settings' }, ];
Deep imports and bundle size
Named imports from glyphfox/vue are tree shaken: the icon modules are marked free of side effects and each component is created with a pure call. In a Vite 8 production build (with Vue itself excluded), one icon component and the shared runtime come to about 53 KB minified, 20 KB gzip, and nine more icons add less than 1 KB gzip. The runtime carries the renderer, morph, fusion and modifier engines and is paid once.
<script setup> // One module, for tools that do not tree shake. import { FoxIcon } from 'glyphfox/vue/icons/fox'; </script> <template> <FoxIcon :size="32" variant="duotone" title="glyphfox" /> </template>
The tree shaking guide explains what keeps the set out of your bundle and what pulls it back in.
Props
Every icon component, Icon, Fusion and MorphIcon takes the shared props. In templates you can write the kebab-case attribute from the second column.
| Prop | In templates | Type | Default | Description |
|---|---|---|---|---|
size | size | number, string | 24 | Width and height; numbers are pixels. |
color | color | string | currentColor | Main color. |
secondaryColor | secondary-color | string | color | Color of the duotone layer and of the solid fill. |
secondaryOpacity | secondary-opacity | number | 0.3 / 1 | Opacity of the duotone layer or of the solid fill. |
weight | weight | number | 2 | Stroke from 1 to 3 on the 24 grid, optically compensated below 24 px. |
absoluteWeight | absolute-weight | boolean | false | Keeps the stroke at weight pixels at any size. |
corners | corners | round, sharp | round | Round or sharp joins and caps. |
variant | variant | outline, duotone, solid | outline | Style axis; solid comes from the closed shapes. |
rotate | rotate | number | 0 | Rotation in degrees. |
flipX | flip-x | boolean | false | Mirror horizontally. |
flipY | flip-y | boolean | false | Mirror vertically. |
mirrorRtl | mirror-rtl | boolean | false | Mirror in right to left layouts. |
animate | animate | draw, pop, wiggle, spin, pulse, bounce, shake | none | Living state preset. |
trigger | trigger | mount, hover, click, state | mount | When the animation or the morph plays. |
active | active | boolean | false | The state for trigger state. |
duration | duration | number (ms) | per preset | Animation or morph duration. |
delay | delay | number (ms) | 0 | Animation or morph delay. |
loop | loop | boolean, number | false | Repeat forever or a number of times (spin and pulse loop by default). |
modifier | modifier | plus, minus, check, x, alert, lock, clock, sync, arrow-up, arrow-down, dot | none | Badge with an automatic knockout gap. |
modifierCorner | modifier-corner | top-left, top-right, bottom-left, bottom-right | bottom-right | Corner of the badge. |
morphTo | morph-to | name, data | none | Morph target; the trigger decides when it shows. |
title | title | string | none | Accessible name: title, role img and aria-label. |
decorative | decorative | boolean | true without a title | Hides the icon from assistive technology. |
Icon adds name, icon (icon data) and gap. Fusion adds icons, layout, gap, scale and offsets. MorphIcon adds name and icon. A default slot renders inside the svg.
Pitfalls
- An icon used by name is empty in production
- The dev server pre-bundles the whole package, so every name resolves while you develop. A production build keeps only what you import, and Vite does not bundle the on demand loader that sits in node_modules: unregistered names in
Icon,Fusion,MorphIconormorph-torender an empty svg. Register them withregisterIcons, as insrc/icons.jsabove. - Icon only buttons without a name
- Icons are decorative by default and get
aria-hidden="true". A button that contains only an icon therefore has no accessible name. Putaria-labelon the button, or give the icon atitle. More patterns in the accessibility guide. - morph-to plays as soon as the page loads
- The default trigger is mount: the icon morphs to its target right after mounting. When the target is the state you want to show immediately, use
trigger="state"with:active, which starts at the right end without an animation. - Boolean props written as strings
active="false"passes the string "false". glyphfox reads it as false, but Vue warns about the prop type. Write boolean props bare (flip-x) or bind them (:active="playing").- Star imports
import * as icons from 'glyphfox/vue'followed byicons[name]keeps every icon in the bundle, because the bundler cannot know which names you read. Build a small map of the components you need instead.
Questions
Does glyphfox work with Vue 2?
The components need Vue 3.2 or newer. In a Vue 2 app, use the <glyph-fox> web component, which takes the same props as attributes; add glyph-fox to Vue.config.ignoredElements so Vue leaves the tag alone. See the web components guide.
Can I use the components with the Options API?
Yes. They are regular components: list them in components: { HomeIcon } and use them in the template. useMorph is a composable, so it belongs in setup().
Can my own SVG icons get the same props?
Yes. createIcon({ name, body, tags, categories }) from glyphfox/vue turns icon data into a component, where body is the inner markup of a 24 by 24 stroke icon. Run your SVG through Fox-ify in the studio first, so it follows the grid, stroke and padding rules that weight and the other axes rely on.