@doitrix/icons

A motion-aware icon system for Vue 3 — 24x24 geometry, variable weight and fill, animated state transitions.
1,353
Install
npm install @doitrix/icons
Latest Version:1.5.0
Vue:3
License:MIT
Last Updated:Oct 8, 2026
Links: npm
Maintainer: venomous-maker-org

@doitrix/icons

An icon system for interfaces that move — 24×24 geometry, variants derived from one shape, and state transitions that read as a single object changing rather than two icons swapping.

bun add @doitrix/icons

No runtime dependencies. Vue is an optional peer, needed only for the Vue layer.

What this is for

Lucide, Heroicons and Material Symbols are excellent, and this does not try to out-draw them — 129 icons against Lucide's 2,000 is not a contest. It exists to do the things a general-purpose set is not trying to do:

One geometry, three variants Outline, duotone and filled are the same paths at different fill strengths — so a variant change is an animatable value, not a swap
Any icon morphs into any other Not curated pairs — home into settings, search into spark. Arbitrary geometry, no hand-alignment
Transitions, not swaps Menu ⇄ Close, Plus ⇄ Close, and a tick that draws on. Continuity is the point
Hit area separated from icon size An 18px icon still gets a 44px target. The most common icon-button mistake, invisible until someone taps it
Journey step states ○ → ◉ → ✓ as one shape, for onboarding progress
Onboarding vocabulary welcome, discover, click, swipe, drag, scroll, tip, shortcut, newFeature
Optical sizing Stroke lightens at small and large sizes so weight reads constant
Accessibility by construction Decorative by default; a label switches it to role="img"
Reduced motion Every icon reaches its final state; it just gets there instantly

Works in any framework

The core is framework-free. Vue components are one adapter, not the only door.

Plain JavaScript, React, Svelte, Angular, Astro

import { renderIcon, createIcon, defineIconElement } from '@doitrix/icons'

renderIcon('arrowRight', { size: 20 })   // SVG markup as a string
createIcon('home', { size: 24 })          // a live SVGElement

Or use the custom element, which every framework renders natively:

import { defineIconElement } from '@doitrix/icons'
defineIconElement()
<dx-icon name="arrow-right" size="20"></dx-icon>
<dx-icon name="close" label="Dismiss"></dx-icon>

Names accept kebab-case in markup and camelCase in code.

Vue

<script setup lang="ts">
import { DxIcon, DxIconButton, DxJourneyStep } from '@doitrix/icons/vue'
import '@doitrix/icons/style.css'
</script>

<template>
  <DxIcon name="arrowRight" :size="20" />
  <DxIconButton icon="close" label="Dismiss" context="toolbar" />
  <DxJourneyStep state="current" />
</template>

The stylesheet is only needed for the animated components.

Props

DxIcon

Prop Type Default
name IconName — Unknown names render nothing rather than breaking layout
size number | string 24
strokeWidth number | string 2
absoluteStrokeWidth boolean false Keep rendered thickness constant as size changes
fill number | boolean 0 0 outline, 1 solid, between is duotone
rotate number 0 About the canvas centre
flip 'horizontal' | 'vertical' | 'both' —
label string — Omit for decorative icons

DxIconButton

Prop Type Default
icon IconName —
label string — Required — an icon-only control has no visible name
context IconContext 'button' toolbar button navigation hero status inline
hitArea number from context Never below 44px
variant IconVariant 'outline'
optical boolean true Adjust stroke for the rendered size

Animated

DxMenuToClose and DxPlusToClose take open; DxCheckmark takes checked and circle; DxArrowMotion takes direction and travel; DxLoading takes duration. All accept size, strokeWidth and label.

DxArrowMotion responds to the parent's hover and focus, so the whole control moves as one object:

<button class="dx-hover">
  Continue <DxArrowMotion direction="right" />
</button>

Morphing

Libraries that morph icons do it by hand: a menu and a close are drawn as the same three lines so CSS can rotate one into the other. That works for a handful of curated pairs and not at all for the rest.

This morphs arbitrary geometry.

<DxMorphIcon :name="current" :size="28" />

Change name and the icon transforms into the new one — any icon, any other icon, with no pre-alignment.

import { planMorph, canMorph } from '@doitrix/icons'

const plan = planMorph(paths.home, paths.settings)
plan?.at(0.5)   // the geometry halfway between them

How it works. Both icons are sampled into equal point counts using the browser's own geometry engine (getPointAtLength), then interpolated. Using the browser rather than a hand-written path parser means arcs, cubics, quadratics and every shorthand come for free, and the sampling cannot drift from how the path actually renders.

Sub-paths are padded on both sides, so a five-path icon can morph into a two-path icon: the surplus collapses to a point instead of vanishing. Point rings are rotated to their nearest alignment first, which is what stops the shape visibly unwinding mid-morph.

Where no geometry engine exists — a server, an old browser, reduced motion — canMorph() reports false and the component swaps instantly. The end state is identical either way.

Icon set

129 icons across twelve groups — navigation, journey, status, onboarding, interface, communication, people, places, work, data, money and meeting.

import { iconNames, iconGroups, isIconName } from '@doitrix/icons'

Motion tokens

import { DURATION, EASE, prefersReducedMotion } from '@doitrix/icons'

DURATION // { micro: 0.18, state: 0.28, morph: 0.36 }

Shared with @doitrix/product-journey so icon motion matches journey motion.

Design rules

24 × 24 canvas
2px centred stroke
round caps and joins
legible at 16px
no gradients, no mixed weights

Size

index.js   1.7 KB gzip   framework-free core
vue.js     2.3 KB gzip   Vue components
paths.js   2.0 KB gzip   geometry only
style.css  2.0 KB gzip   animated components only

Prior art and credits

This set stands on conventions established by others, and it is worth naming them.

Lucide (ISC) — the design language here follows Lucide's published guidelines: a 24×24 canvas, 2px centred stroke, round caps and joins. The absoluteStrokeWidth prop is Lucide's name for that behaviour and is kept deliberately, because developers already know it.

Material Symbols (Apache-2.0) — the idea of an icon varying continuously along axes such as fill and weight, rather than shipping a separate file per state, comes from Material's variable font work.

Heroicons (MIT) — for the outline/solid pairing that this generalises into a single fill value.

On geometry overlap

Every path here was authored by hand rather than copied. That said, this set was diffed against all 2,025 Lucide icons, and 21 of its 126 paths are identical — all of them primitive: straight lines, dots, and a chevron.

A horizontal line across a 24-unit grid has one sensible form; so does a plus, a minus, and M12 8h.01 for a dot. Those matches are convergence, not authorship. No multi-curve icon matched. The comparison is reproducible if you want to run it yourself.

Universal shapes — a house for home, a magnifier for search, three bars for a menu — are shared visual vocabulary and belong to no one.

License

MIT

Related Packages

ki-icons

Hand-drawn, full-colour food, category and trophy illustrations: a Nuxt module a...

200 0
@aautcq/nuxt-iconify-fontawesome

Use Font Awesome icons with popular Nuxt modules Nuxt Icon and NuxtUI

2,279 0
nuxt-icons

A Nuxt 4 module for easy SVG icons

546,420 194
@bitrix24/b24icons-nuxt

Bitrix24 SVG icons for development web-applications for Nuxt

32,486 6
nuxt3-svg-icons

A simple module to implement your own svg icons in nuxt3.

849

Version History

Version Released Vue License
1.5.0 ^3.3.0 MIT
1.4.1 ^3.3.0 MIT
1.4.0 ^3.3.0 MIT
1.3.0 ^3.3.0 MIT
1.2.0 ^3.3.0 MIT