Getting started
Five steps from an empty directory to a coloured button on screen. Everything deeper lives on the neighbouring pages.
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
- Node 22 or newer. That is what the package declares in
engines: Yarn 1 refuses to install it on an older Node, npm and pnpm only warn. The package itself is ESM-only —"type": "module"and not a singlerequirecondition inexports. - An application built on ES modules. The package has no CommonJS build and is loaded with
import, as Vite does."type": "module"in the application’spackage.jsonis recommended but never checked: the granum config here is.mjsanyway. - Vue 3.5 or newer and a Vite 8 build — that is the peer
@feugene/granumdeclares. Other bundlers are not forbidden, but they are not verified either.
Install
npm i @feugene/granularity vue @floating-ui/dom @unocss/resetpnpm add @feugene/granularity vue @floating-ui/dom @unocss/resetyarn add @feugene/granularity vue @floating-ui/dom @unocss/resetbun add @feugene/granularity vue @floating-ui/dom @unocss/resetAnd the part that only runs at build time and never reaches the bundle:
npm i -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tscpnpm add -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tscyarn add -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tscbun add -d vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tsc@floating-ui/dom is a required peer dependency, not a recommendation. It
positions every floating panel — GrSelect, GrAutocomplete, GrTreeSelect,
GrDropdown, GrDropdownMenu, GrContextMenu, GrTooltip, GrPopover,
GrColorPicker — and everything that renders one, such as GrPagination,
GrDataTable and GrSidebar. Without it the build stops at the first such
import: Vite cannot resolve @floating-ui/dom. 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 — granum assembles it. It reads
the package’s contents from the manifest the package declares as an export and
never walks node_modules: it opens the package’s files only when the manifest
cannot be trusted, and only the files the manifest lists. There is exactly one
source of truth:
import { windEngine } from '@feugene/granum-engine-wind'
import { defineGranumConfig } from '@feugene/granum/vite'
export default defineGranumConfig({
engine: windEngine(),
providers: ['@feugene/granularity'],
components: [{ provider: '@feugene/granularity', names: ['GrButton'] }],
themes: { names: ['light', 'dark'] },
appSources: { dirs: ['src'] },
})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.
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import { granum } from '@feugene/granum/vite'
import granumConfig from './granum.config.mjs'
export default defineConfig({ plugins: [Vue(), granum(granumConfig)] })The config is read in one place, not two. The v1 preset had to be handed the same
object both in content and in presets: let them drift apart and components
arrived unstyled, with the build saying nothing. That fork is gone.
Two imports, and the order between them is load-bearing
import './reset.css'
import 'virtual:granum.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')@import '@unocss/reset/tailwind-compat.css' layer(reset);The reset goes into its own layer, and that is not decoration. granum puts
everything of its own into granum.* layers, and unlayered CSS beats ANY
@layer regardless of specificity: outside a layer, button { color: inherit }
from the reset would override the component’s own utilities, and the button would
arrive in the page’s text colour.
The layer must be declared before granum’s — a layer’s place in the cascade is
fixed by its first appearance — which is why reset.css is imported first.
Getting the order wrong produces neither an error nor a warning.
There is one virtual CSS module now — virtual:granum.css. The tokens, the base
layer, the themes, the component styles and the utilities their templates are
drawn with all arrive in a single sheet: forgetting half the foundation and
getting colours without geometry is no longer possible.
The first component
<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. granum rejects a component from
the root as a build error, ComponentRootImportError.
Run vite. The button should be coloured, with rounded corners and a focus ring
on Tab. If it is grey and bare, the sheet is not imported at all. If the
background is there but the text is in the page’s colour and the padding is
gone, the reset is imported after the sheet. Both cases are 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.