# Performance

> Granularity is the product's main promise. This page shows what it is measured in and how to verify it yourself.

- Page: https://granularity.tech/docs/performance
- Last reviewed: 2026-10-27

The promise is short: **the bundle grows with what you use, not with what the
package contains**. Below is what backs it, what it actually costs and how to
measure it in your own project without taking anyone's word.

## Two sides, and they do not correlate

A component's weight is code plus styles, and the two live apart:

| Component | CSS added to the sheet | JS, the component itself |
|---|---:|---:|
| `GrCard` | 764 B | 3 483 B |
| `GrDialog` | 1 111 B | 11 779 B |
| `GrDataTable` | 4 398 B | 37 790 B |
| `GrSelect` | 4 311 B | 24 283 B |

A component heavy in code may add almost nothing to the sheet, and the other way
round. So "the weight of a component" is not a single number, and any table with
one column is lying. The numbers are the same as on the home page: a measuring
script takes them from the core, not a person.

## Code: a subpath instead of a barrel

```ts
// one component and its graph
import { GrButton } from '@feugene/granularity/components/GrButton'

// the whole package — granum rejects this import: ComponentRootImportError
import { GrButton } from '@feugene/granularity'
```

The top line is granularity itself. A subpath's weight is the gzip of everything
it pulls from `dist`, nested components included, but without external packages
and without CSS. The lightest subpath is `GrSkeleton`, at 1.7 kB: a loading
placeholder needs no neighbouring component. The heaviest is `GrDataTable`, at
121.6 kB. The whole package from the root is 646.5 kB.

:::callout{type="warning"}
**Subpath weights must not be added up.** The shared chunk is counted afresh in
every row but paid for once: the sum of the five heaviest rows is 504.3 kB, while
together they weigh 251.8 kB. A set is measured as a union rather than a sum —
and that is an upper bound: the application's bundler shakes the tree further and
minifies again.
:::

The full table for every subpath is generated from the built `dist` on every
release — it ships inside the package and appears on each component's page in the
[catalogue](/components).

## Styles: you pay for what you selected

CSS is not imported as files — granum assembles it for exactly the components you
listed in `components` and for their dependencies. What each component needs — its
classes, its own `styles.css`, its tokens — is recorded in the package manifest,
and the sheet is built from the manifests rather than from `node_modules`. The
spending breaks down like this:

:::steps
1. **The first component brings the foundation** — the tokens, the base layer, the preflight, the theme with its default mode. That is a one-off.
2. **Each one after it adds its own few hundred bytes** — from seven hundred for a card to four kilobytes for a select.
3. **The second mode costs less than a kilobyte**, not double: modes differ in role values, not in rules.
:::

Not listing `components` at all is a working option: every component of the
provider then reaches the sheet. The difference between "everything" and "one" on
the shared sheet is under fourfold rather than a hundredfold, because the
foundation is shared. Granular styles are worth having, but the effect is more
modest than it is for code, and saying so honestly is cheaper than explaining it
later.

## Runtime

- **Under the core sits only its own token package.** It is the core's single
  dependency, it has none of its own, and the core does not import its code at
  run time: the build takes the values from it. The only mandatory external
  library is `@floating-ui/dom`, and it is declared as a peer dependency of the
  application so that it never arrives as a second copy.
- **A component's CSS arrives through the manifest, not through the chunk.** The
  package's JS chunks import no styles: granum takes the component's `styles.css`
  from the manifest and places it in the `granum.components` layer — only for the
  selected components. There is no separate style import, and that is not a
  convenience but a condition: otherwise granularity would break on the first
  forgotten import.
- **The per-component token registry and the test helpers are separate subpaths**
  (`/tokens`, `/testing`), and the core barrel does not import them. Code
  highlighting is not in the core at all: it lives in
  `@feugene/granularity-code/highlight`.

## How to measure it yourself

:::steps
1. Build the application with one component and measure the gzip of the emitted CSS and JS.
2. Add a second component and measure again. The difference is its real cost in your project.
3. Compare against the barrel: granum will not let a root import through, so switch the guard off for the measurement (`js: { guard: 'off' }`) and repeat. The difference is what the subpath spares you.
:::

The numbers on this site are taken by scripts rather than by building an
application. In the table at the top of the page, granum builds the sheet from
the core's manifest with no application sources, and esbuild builds the code from
the component's subpath with `vue`, `@floating-ui/dom` and `@feugene/fint-i18n`
left external. So treat them as an order of magnitude: your result depends on the
bundler version, the minifier settings and on what else is in the graph.

## What will speed nothing up

- **`importStyle: true` on the auto-import resolver.** The core does not need it:
  granum has already taken the components' styles from the manifests, and the
  core publishes no `components/<Name>/styles.css` subpaths — the option, once
  enabled, would break the build.
- **Manual `styles/*.css` and `themes/*.css` imports** on top of the granum sheet. They add weight
  rather than removing it: granum has already placed the tokens, the base and the
  theme's modes in its layers, and a file imported outside the layers will also override
  them in the cascade.
- **Narrowing a theme's `modes` to one mode** to save space. The second mode costs
  less than a kilobyte — this is not the place to give up mode switching.
