Hovue: Building an Animated Vue Icon Library with Motion Baked In
Icons are everywhere in modern web apps. We use them for navigation, actions, status indicators, and visual cues. But most icon libraries treat them as static assets, lifeless SVGs that just sit there.
I wanted something different. I wanted icons that feel alive. That’s why I built Hovue.
The Problem with Static Icons
When building interfaces, I found myself constantly adding hover effects and micro-interactions to icons manually. Every arrow needed a little slide animation. Every bell needed a subtle bounce. Every refresh icon needed a rotation.
This meant:
- Writing custom CSS for each icon
- Managing animation states across components
- Inconsistent timing and easing values
- A lot of repetitive code
What if the animations were already there?
Introducing Hovue
Hovue is an animated Vue 3 icon library with motion baked in. Every icon ships with five animation types, and you control them through simple props.
<HoArrowRight animation="slide" />
<HoBell animation="bounce" />
<HoRefresh animation="rotate" />
<HoHeart animation="pulse" />
<HoMail animation="fade" />
No extra CSS. No animation libraries. Just props.
Five Animation Types
Each icon supports five animation types, designed for specific use cases:
| Animation | Effect | Best For |
|---|---|---|
slide | Horizontal translation | Arrows, navigation |
bounce | Vertical bounce | Notifications, success states |
rotate | 180° rotation | Loading, refresh actions |
pulse | Subtle scale pulse | Alerts, live indicators |
fade | Opacity transition | State changes, transitions |
Full Customization
Every aspect of the icon is controllable via props:
<HoCheck
:size="32"
color="#10b981"
animation="bounce"
:animation-duration="400"
/>
The size prop accepts both numbers (pixels) and strings (any CSS unit). The color defaults to currentColor, so icons inherit text color automatically.
Zero Dependencies, Pure CSS
A conscious decision I made early: no JavaScript animation libraries.
All animations are pure CSS, which means:
- Better performance: No runtime overhead
- Predictable behavior: CSS animations are battle-tested
- Smaller bundle: Each icon is less than 2KB gzipped
- Native feel: Hardware-accelerated transforms
Here’s what the animation system looks like under the hood:
.hovue-animation-slide {
transition: transform var(--hovue-duration) ease;
}
.hovue-animation-slide:hover {
transform: translateX(4px);
}
.hovue-animation-bounce:hover {
animation: hovue-bounce 0.6s ease;
}
@keyframes hovue-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
First-Class Nuxt Support
For Nuxt users, there’s a dedicated module that auto-imports all icons:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@hovue/nuxt']
})
That’s it. Every icon is now available globally with the Ho prefix (configurable).
<!-- No imports needed -->
<template>
<HoHome animation="bounce" />
</template>
The Icon Collection
Hovue currently ships with 36+ icons across six categories:
- Arrows: ArrowRight, ArrowLeft, ChevronRight, ChevronDown
- Actions: Check, X, Plus, Minus, Search, Menu
- UI Elements: Home, User, Settings, Bell, Mail, Heart
- Media: Play, Pause, Volume, Camera, Image, Music
- Files: File, Folder, Copy, Trash, Download, Upload
- Loaders: Loader, Refresh, Sync, Clock, Calendar, Star
Each icon is a standalone Vue component, fully tree-shakeable. Import only what you need.
Getting Started
Install with your package manager of choice:
# npm
npm install @hovue/icons
# pnpm
pnpm add @hovue/icons
# yarn
yarn add @hovue/icons
Import and use:
<script setup>
import { HoArrowRight, HoBell, HoHeart } from '@hovue/icons'
</script>
<template>
<button>
Next <HoArrowRight animation="slide" />
</button>
</template>
Architecture Decisions
Building Hovue taught me a few things about library design:
1. Props Over Classes
I initially considered using CSS classes for animations (class="animate-bounce"), but props are more Vue-idiomatic and provide better TypeScript support.
2. Monorepo Structure
The project uses pnpm workspaces and Turborepo:
packages/
├── icons/ # Core library
├── nuxt/ # Nuxt module
├── cli/ # CLI tool
└── website/ # Documentation
This keeps concerns separated while sharing code through workspace dependencies.
3. CSS Variables for Duration
Using --hovue-duration as a CSS custom property lets users control animation timing without JavaScript:
<HoLoader
animation="rotate"
:animation-duration="1000"
/>
The duration prop sets the CSS variable, which the animation rules reference.
What’s Next
Hovue is open source and actively maintained. Some things on the roadmap:
- More icons (aiming for 100+)
- Additional animation types
- Animation direction control
- Stagger animations for icon groups
- React port (maybe?)
Try It Out
If you’re building Vue or Nuxt apps and want icons with personality, give Hovue a try:
- GitHub: https://github.com/Hovue/hovue
- Documentation: hovue.xyz
- icons: @hovue/icons
- nuxt module: @hovue/nuxt
- cli tool: @hovue/cli
Icons don’t have to be boring. With Hovue, they come alive.
Hovue is MIT licensed and open for contributions. If you find it useful, consider starring the repo or contributing icons!
Enjoyed this post? Check out more articles on my blog.
View all posts
Comments