Getting started

Five steps from an empty directory to a coloured button on screen. Everything deeper lives on the neighbouring pages.

Machine-translated, not yet reviewed. Read the original

The shortest useful page on this site. No options, no caveats, no “but what if you are on Nuxt” — one path that gets you to a working component. The branches start on installation and in configuration.

What you need

  1. Node 22 or newer. Below that the package does not install: it is ESM-only and says so in engines.
  2. "type": "module" in the application’s package.json. CommonJS is not supported.
  3. Vue 3.5 or newer and a Vite build. Other bundlers are not forbidden, but they are not verified either.

Install

npm i @feugene/granularity vue @floating-ui/dom @unocss/reset
pnpm add @feugene/granularity vue @floating-ui/dom @unocss/reset
yarn add @feugene/granularity vue @floating-ui/dom @unocss/reset
bun add @feugene/granularity vue @floating-ui/dom @unocss/reset

And the part that only runs at build time and never reaches the bundle:

bash
yarn add -D unocss @feugene/unocss-preset-granular @unocss/preset-mini

@floating-ui/dom is a required peer dependency, not a recommendation. It is what positions GrSelect, GrDropdown, GrAutocomplete, GrTreeSelect, GrTooltip and GrPopover. Without it the application throws on the first import of a dropdown. The modal layer, by contrast, needs nothing: the focus trap, inert and the Esc order are the package’s own primitives.

Configuration

The design system’s CSS is not imported as files — UnoCSS assembles it from the granular provider, and the provider reads the package’s built dist. So there is exactly one source of truth:

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

// One object for both calls. Let them drift apart and components arrive unstyled.
const granular = {
  providers: [granularityProvider],
  components: [{ provider: '@feugene/granularity', names: ['GrButton'] }],
  themes: { names: ['light', 'dark'] },
  layer: 'granular' as const,
}

export default defineConfig({
  // `@unocss/vite` reads `content` from the top level of the config only,
  // not from the preset. Skip it and the extractor never looks into `dist`.
  content: granularContent(granular),
  presets: [presetMini(), presetGranularNode(granular)],
})

components can be left out entirely — then every component of the provider lands in the CSS. Listing them narrows the sheet down to the set you need, and this is the one place where you pay for granularity with attention.

vite.config.ts
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import UnoCSS from 'unocss/vite'

export default defineConfig({ plugins: [Vue(), UnoCSS()] })

Three imports, and the order between them matters

src/main.ts
import '@unocss/reset/tailwind-compat.css'
import 'virtual:uno:granular.css'
import 'virtual:uno.css'

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

There are two virtual modules and both are required. The granular layer holds the foundation — :root { --gr-* }, the base layer, the themes, per-component variables. The utility classes the component templates are drawn with are generated into the shared virtual:uno.css. Forget the second import and you get components with tokens but without layout: the colours are there, the geometry is not.

Without layer: 'granular' there is no separate layer module and everything arrives in a single virtual:uno.css — that is correct too. The layer is for when the order relative to your own CSS matters.

The first component

src/App.vue
<script setup lang="ts">
import { GrButton } from '@feugene/granularity/components/GrButton'
</script>

<template>
  <GrButton tone="primary">Save</GrButton>
</template>

Importing from a subpath rather than from the package root is granularity itself. It pulls one component and its graph, not a barrel.

Run vite. The button should be coloured, with a shadow and a focus ring on Tab. If it is grey or bare, it is almost certainly one of the three CSS imports; that exact case is worked through on the troubleshooting page.

Next

  • Installation — why the dependency list looks like this, icons, optional integrations.
  • Configuration — defaults, locale, overrides at the application level.
  • Theming — rebranding without touching components.
  • Component catalogue — what there is at all.

Last reviewed: 2026-09-01