Installation

What gets installed, why exactly that, and where it goes — dependencies or devDependencies. Plus icons and optional integrations.

Machine-translated, not yet reviewed. Read the original

Getting started gives you one working path with no explanations. This page explains: why the dependency list looks the way it does, what of it reaches the bundle and what only ever runs at build time.

Requirements

WhatVersionWhy it is strict
Node≥ 22Declared in engines, the package is ESM-only
"type": "module"requiredThere is no CommonJS build and none is planned
Vue^3.5Peer dependency
@floating-ui/dom^1.8Required runtime peer dependency
UnoCSS≥ 66Together with @unocss/preset-mini or @unocss/preset-wind4

@feugene/fint-i18n is a peer too, but an optional one: without it the components speak English through their built-in strings rather than failing. Details are on the localization page.

The one supported way

Everything goes through the UnoCSS preset presetGranularNode and the package’s granular provider. Importing CSS files directly, importing “everything at once” from the root and pulling in styles.css around the preset are not supported — they work right up to the first upgrade and then break silently.

There is one exception, and the package names it itself: @feugene/granularity/styles.css is a ready-made sheet with the tokens, the base layer, the preflight and both themes, for consumers who do not run the preset at all. It gives you a working base, but not the utility classes the component templates are drawn with.

What goes where

bash
# in dependencies — it ends up inside the application
yarn add vue @feugene/granularity @floating-ui/dom @unocss/reset

# in devDependencies — it only ever runs at build time
yarn add -D unocss @feugene/unocss-preset-granular @unocss/preset-mini

@feugene/granularity goes into dependencies: its components are imported from your sources and land in the bundle.

@floating-ui/dom goes into the application’s dependencies, not inside the package. It is what positions GrDropdown, GrSelect, GrAutocomplete, GrTreeSelect, GrTooltip and GrPopover. The package keeps the library external and does not bundle it into dist — otherwise an application that already uses floating-ui would end up with a second copy.

The preset, unocss and preset-mini go into devDependencies: they run inside uno.config.ts and not a single line of them reaches the bundle. The granularityProvider is re-exported by the package itself and needs no dependencies of its own.

The modal layer — GrModal, GrDialog, GrDrawer, GrImageViewer, GrCommandPalette — needs no external dependencies at all. The focus trap, inert for the background, the Esc order and focus restoration are the package’s own primitives. @headlessui/vue was dropped in 0.15.0 and is not a peer dependency: if it is still installed for this package alone, it can go.

Growing the config

The minimal working config is the provider and nothing else:

uno.config.ts
import { defineConfig, presetMini } from 'unocss'
import { presetGranularNode } from '@feugene/unocss-preset-granular/node'
import granularityProvider from '@feugene/granularity/granular-provider/node'

export default defineConfig({
  presets: [presetMini(), presetGranularNode({ providers: [granularityProvider] })],
})

That already mixes in tokens.css and base.css, enables every component of the provider with its preflight, and brings up the rules, variants and safelist. From there you narrow and refine with options:

OptionWhat it does
componentsA list of components instead of “all”. The one place where you pay for granularity with attention
themes.namesWhich themes reach the sheet. By default every theme of the provider
themeFiles, tokensFile, baseFileReplace the theme CSS, the tokens or the base layer with your own files
layerPuts the package preflight into a separate layer and gives it its own virtual module

granularContent(options) in the top-level content is required whenever the components arrive as subpaths from a built dist: otherwise the UnoCSS extractor never looks there. It is read from the top level of the config only, not from the preset.

Icons

The package brings its own icons with it. The select arrow, the clear cross, the check mark on the chosen option, the spinner and the drag handle are compiled into dist when the package is built. You do not need unplugin-icons or an icon collection for them.

Your own icon arrives in one of two ways. The icon props take either a Vue component or a class:

vue
<script setup lang="ts">
import IconUser from '~icons/lucide/user'
</script>

<template>
  <!-- Component: always works, nothing to configure. -->
  <GrTabs :tabs="[{ value: 'a', label: 'Profile', icon: IconUser }]" />

  <!-- Class: the CSS for it is generated by your config, not by the package. -->
  <GrTabs :tabs="[{ value: 'a', label: 'Profile', icon: 'i-lucide-user' }]" />
</template>

i-lucide-* is a UnoCSS utility, and your build is what makes it. Without presetIcons and an icon collection the class stays a class: the space for the icon is there, the picture is not, and the build passes in silence.

bash
yarn add -D @unocss/preset-icons @iconify-json/lucide

Optional integrations

  • @feugene/unplugin-granularity — a resolver for unplugin-vue-components: auto-import of components and directives in templates. It inserts static imports into the SFC, so tree-shaking stays as tight as it was.
  • createGranularity from @feugene/granularity/vue — a runtime adapter for directives used in render/JSX, for a shared provide and for globalProperties. It imports no components itself: what you pass is what gets registered.

Both complement the main way of installing rather than replacing it. Setting up either one is covered in configuration.

Last reviewed: 2026-09-01