Design system for Vue 3

Small pieces. Stronger systems.

Build consistent interfaces from composable components, foundations and utilities — importing exactly what you use, and nothing else.

Vue 3.5 · TypeScript · 84 components · dependencies: 1

Two axes, not one
ComponentCSSJS
GrCard+ 764 B3 483 B
GrDialog+ 1 111 B11 779 B
GrDataTable+ 4 398 B37 790 B
GrSelect+ 4 311 B24 283 B

GrDataTable and GrSelect cost nearly the same CSS, and their JS differs by more than half again. One number does not predict the other.

Gzipped, package only — your framework is yours and is not counted here.

Not a screenshot

A working screen, assembled from the same components.

Store overview

Revenue
€21,480
Increase+12.4% vs previous
Orders
486
Increase+6.1% vs previous
Conversion
3.4%
Decrease−0.3 pp vs previous
Average basket
€44.20
No changeNo change

Daily revenue

Thousands of euros

The same demo that stands on the GrStatistic page — nothing was written twice for the sake of a homepage.

Composable

147 export paths. Import a component, a composable or a single directive — the graph stops there.

Consistent

Every colour, space and radius is a role, not a value. Both modes are first-class.

Customizable

Rebrand by changing token values. Component source stays untouched.

Developer-first

Types, auto-import, IDE metadata and an accessibility contract that is tested, not claimed.

The same cost, four ways

One number would have been easier to print.

Every figure here comes from a build, gzipped, with the framework excluded — it is yours, not ours. Which of these four matters depends on what constrains you, so all four are on the page.

What adoption costs

8 529B, then almost nothing

base + GrButton8 529 B
+ GrCard+ 764 B
+ GrDialog+ 1 111 B
+ GrDataTable+ 4 398 B
+ GrSelect+ 4 311 B

The first component carries tokens, both modes and the base layer. The next four together still cost less than it alone.

Whether every component is cheap

15.7×spread across the set

GrBadge2 402 B
GrCard3 483 B
GrAlert4 424 B
GrButton4 913 B
GrTabs5 432 B
GrDialog11 779 B
GrSelect24 283 B
GrDataTable37 790 B

No. The spread runs from GrBadge to GrDataTable, and the figure above is how far. A library that claims every component is light is not measuring.

What you take on

1dependency — its own tokens

147 export paths

Under the node sits only the family’s own token package, and nothing sits under it. Your lockfile, your audit surface and your upgrade path stay yours — and 147 export paths mean the graph stops where you stop.

What a second mode costs

560B, at any size

1 · 7 969 B
2 · 8 529 B

The figure barely moves between one component and all 84. Dropping dark mode is not where weight is saved.

Measured 2026-10-28 · core 3.7.2 · granum 3.13.0 · gzip

The model

Four layers, each built from the one below it. The name is the architecture.

01

Tokens

Colour, space, radius, motion and layering as named roles.

02

Components

84 in the core, and companions around it. Each one importable alone.

03

Patterns

Compositions that solve a task: forms, search, settings, overlays.

04

Interfaces

Whole screens. Components are useful; systems are valuable.

Foundations

A token is a role, not a hex code.

Colour, space, radius, motion and layering are named roles read from the package. Change the values and the components follow — in both modes.

tokens
211
modes
2
foundations
10

Explore foundations →

RoleLightDark
--gr-bg#f8fafc#0f172a
--gr-card#ffffff#1e293b
--gr-fg#0f172a#f8fafc
--gr-muted-fg#556275#aab6c7
--gr-primary#4f46e5#9d8bfa
--gr-accent#eef2ff#1e1b4b
One markup, two modes: the card is drawn from roles only, and the mode attribute is the only difference.

From nothing to a button

Four steps, no generator.

The whole getting started path: the packages, the granum config, two CSS imports and the first component. No scaffolding tool writes any of it for you.

Steps

For the app

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

For the build

npm i -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tsc
pnpm add -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tsc
yarn add -D vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tsc
bun add -d vite @vitejs/plugin-vue @feugene/granum @feugene/granum-engine-wind typescript vue-tsc
granum.config.mjs
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'] }],
  theme: 'slate',
  modes: { names: ['light', 'dark'] },
  appSources: { dirs: ['src'] },
})
vite.config.ts
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)] })
src/main.ts
import './reset.css'
import 'virtual:granum.css'

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

createApp(App).mount('#app')
src/reset.css
@import '@unocss/reset/tailwind-compat.css' layer(reset);
src/App.vue
<script setup lang="ts">
import { GrButton } from '@feugene/granularity/components/GrButton'
</script>

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

Get startedTry it in the playground

Open development

Everything here is checkable.

The figures on this site come from the build, not from copy. Each one leads to the page that shows how it is made.

Start building with Granularity.

The shortest useful page is the getting started one. The rest of the documentation waits until you need it.

Get startedOpen the playground

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

Why the list looks like this