# Budgets

> Per-primitive and per-entry-point budgets, targets marked as targets until measured, and the listener budget for 1 versus 1000 roots.

Page: https://svelte-lean.vercel.app/performance/budgets · Markdown: https://svelte-lean.vercel.app/performance/budgets.md · Index: https://svelte-lean.vercel.app/llms.txt

A budget is the value a size script fails on, read from the same artifact file as the measured value.

## How budgets work

- Each package's `yarn size` builds its entries, writes budget and measured size to `artifacts/size.json` and exits with an error over budget; `yarn verify` runs it on every pull request.
- A budget sits ten to fifteen percent above the value measured when it was set (behavior budgets: measured plus ten percent, rounded up to the next hundred bytes), so a regression fails CI and a compressor update of a few bytes does not.
- A budget is raised only with the reason in the commit and a changeset ([Methodology](https://svelte-lean.vercel.app/performance/methodology.md#regression-thresholds)).

## Behavior JavaScript

Each Tier 1 and Tier 2 registration module, Vite production build, shared kernel included. Brotli. The script also fails on a diagnostic marker in the bundle or a lost registration side effect.

| Subject | Entry | Budget | Measured | Headroom | Status |
| --- | --- | --- | --- | --- | --- |
| `tabs` | vite | 1900 B brotli | 1839 B brotli | 61 B | within budget |
| `menu` | vite | 2800 B brotli | 2773 B brotli | 27 B | within budget |
| `listbox` | vite | 2600 B brotli | 2357 B brotli | 243 B | within budget |
| `select` | vite | 5300 B brotli | 5022 B brotli | 278 B | within budget |
| `autocomplete` | vite | 4000 B brotli | 3962 B brotli | 38 B | within budget |
| `calendar` | vite | 5000 B brotli | 4594 B brotli | 406 B | within budget |
| `date-picker` | vite | 10200 B brotli | 9475 B brotli | 725 B | within budget |
| `toast` | vite | 1800 B brotli | 1616 B brotli | 184 B | within budget |
| `tree` | vite | 2400 B brotli | 2140 B brotli | 260 B | within budget |
| `slider` | vite | 1700 B brotli | 1549 B brotli | 151 B | within budget |
| `input` | vite | 2000 B brotli | 1808 B brotli | 192 B | within budget |
| `checkbox-group` | vite | 1600 B brotli | 1456 B brotli | 144 B | within budget |
| `field/validation` | vite | 1900 B brotli | 1714 B brotli | 186 B | within budget |
| `toggle-group` | vite | 2200 B brotli | 1932 B brotli | 268 B | within budget |
| `toolbar` | vite | 1800 B brotli | 1706 B brotli | 94 B | within budget |
| `splitter` | vite | 2000 B brotli | 1908 B brotli | 92 B | within budget |
| `sortable` | vite | 4300 B brotli | 3833 B brotli | 467 B | within budget |
| `menu/contextmenu` | vite | 1800 B brotli | 1631 B brotli | 169 B | within budget |
| `file-drop` | vite | 3300 B brotli | 2983 B brotli | 317 B | within budget |
| `hover-card` | vite | 2000 B brotli | 1986 B brotli | 14 B | within budget |
| `menubar` | vite | 2100 B brotli | 2064 B brotli | 36 B | within budget |
| `hint` | vite | 2900 B brotli | 2687 B brotli | 213 B | within budget |
| `copy` | vite | 1300 B brotli | 1291 B brotli | 9 B | within budget |
| `findable` | vite | 1900 B brotli | 1887 B brotli | 13 B | within budget |
| `select+search` | vite | 5500 B brotli | 5461 B brotli | 39 B | within budget |
| `select+search+create` | vite | 5600 B brotli | 5573 B brotli | 27 B | within budget |
| `anchor-position` | vite | 1800 B brotli | 1741 B brotli | 59 B | within budget |
| `popover/anchor` | vite | 1450 B brotli | 1401 B brotli | 49 B | within budget |

Script: `packages/primitives/scripts/size.mjs`; values from `packages/primitives/artifacts/size.json`.

Method

ESM, target es2022, gzip level 9, brotli quality 11 (node:zlib). Tier 1 and Tier 2 entries are dist/\<name\>/register.js with @svelte-lean/core from the workspace. "vite" builds the public specifier with Vite in production mode; the brotli budget applies to it and it must list no diagnostics and keep the registration. "esbuild" bundles the same file with esbuild under the production condition; it keeps development helpers core references from folded DEV gates and is recorded for comparison. "core-only" is a registration with an empty handler. "native-only" imports the root entry and a Tier 0 subpath and must contain no runtime.

## Kernel

The @svelte-lean/core root entry (production condition and runtime-detected environment) and what one Tier 1 behavior pulls in under Vite. Gzip, all three.

| Subject | Entry | Budget | Measured | Headroom | Status |
| --- | --- | --- | --- | --- | --- |
| `production` | esbuild | 2750 B gzip | 2637 B gzip | 113 B | within budget |
| `default` | esbuild | 2750 B gzip | 2726 B gzip | 24 B | within budget |
| `consumer` | vite | 2750 B gzip | 1239 B gzip | 1511 B | within budget |
| `reorder` | esbuild | 1950 B gzip | 1752 B gzip | 198 B | within budget |

Script: `packages/core/scripts/size.mjs`; values from `packages/core/artifacts/size.json`.

Method

ESM, target es2022, gzip level 9, brotli quality 11 (node:zlib). "production" and "default" are dist/index.js with every export, esbuild bundle + minify, under the production export condition and the runtime-detected env module respectively. "consumer" is one Tier 1 behavior using parts and emit, built with Vite in production mode; it must list no diagnostics. "reorder" is dist/reorder.js (the @svelte-lean/core/reorder subpath), esbuild bundle + minify.

## Table entry points

Every @svelte-lean/table entry bundled alone with esbuild, Svelte excluded. Shared code counts once per entry here; a consumer bundler de-duplicates it. Gzip.

| Subject | Entry | Budget | Measured | Headroom | Status |
| --- | --- | --- | --- | --- | --- |
| `renderer` | dist/index.js | 24900 B gzip | 24816 B gzip | 84 B | within budget |
| `headless` | dist/core.js | 2700 B gzip | 2665 B gzip | 35 B | within budget |
| `engine` | dist/table.svelte.js | 3600 B gzip | 3538 B gzip | 62 B | within budget |
| `grouping` | dist/grouping.js | 2400 B gzip | 2149 B gzip | 251 B | within budget |
| `pivot` | dist/pivot.js | 600 B gzip | 526 B gzip | 74 B | within budget |
| `editing` | dist/editing.js | 800 B gzip | 707 B gzip | 93 B | within budget |
| `csv` | dist/export.js | 850 B gzip | 741 B gzip | 109 B | within budget |
| `clipboard` | dist/clipboard.js | 1000 B gzip | 895 B gzip | 105 B | within budget |
| `server` | dist/server.js | 425 B gzip | 369 B gzip | 56 B | within budget |
| `state` | dist/state.js | 900 B gzip | 796 B gzip | 104 B | within budget |
| `styles` | dist/style.css | 9100 B gzip | 9086 B gzip | 14 B | within budget |

Script: `packages/table/scripts/size.mjs`; values from `packages/table/artifacts/size.json`.

Method

Each entry bundled independently with esbuild (bundle + minify, ESM); shared code is counted per entry here and de-duplicated by consumer bundlers. Svelte peer runtime excluded. gzip level 9 / brotli quality 11 (node:zlib). Budgets are gzip.

## Styles

The resolved, minified index.css. Gzip, whole package; single files are listed on Results without a budget.

| Subject | Entry | Budget | Measured | Headroom | Status |
| --- | --- | --- | --- | --- | --- |
| `index.css (resolved)` | – | 31300 B gzip | 31124 B gzip | 176 B | within budget |

Script: `packages/styles/scripts/size.mjs`; values from `packages/styles/artifacts/size.json`.

Method

bytes = shipped source; minified = comments and redundant whitespace removed by scripts/check.mjs minify (a bundler minifier may be slightly smaller); gzip level 9 and brotli quality 11 of the minified text. standalone = the file with the files it @imports inlined. "index.css (resolved)" inlines every @import once, depth first, and carries the budget.

## Targets

Initial targets of ADR 0003, aspirational, quoted from the ADR and compared with the measured values.

> Tabs \< 1 kB brotli, basic Menu ~1–2 kB, entire common behavior set \< 5 kB.

| Subject | Target (aspirational) | Measured | Enforced budget | Result |
| --- | --- | --- | --- | --- |
| Tabs registration with the kernel | 1000 B brotli | 1839 B brotli | 1900 B brotli | above the target, within the enforced budget |
| Menu registration with the kernel | 1000–2000 B brotli | 2773 B brotli | 2800 B brotli | above the range, within the enforced budget |
| Entire common behavior set (tabs and menu on one page, one kernel) | 5000 B brotli | 4600 B brotli | none: measured by the tabs-and-menu fixture, not gated | within the target |
| Native primitives (button, dialog, popover, accordion, checkbox, switch, radio group) | no behavior JavaScript | 0 B brotli | the native-only fixture forbids every runtime module | met |

A target is a direction; the enforced budget is the gate a release may not exceed.

## Tier budgets

Each tier is a runtime budget, not a byte budget (ADR 0003).

| Tier | Name | Budget |
| --- | --- | --- |
| 0 | Native | No runtime JavaScript, no instance listeners, no instance state objects |
| 1 | Delegated micro-behavior | One shared listener per required event type; no per-root listener; DOM-encoded state |
| 2 | Lazy scoped controller | WeakMap state created on first interaction; AbortController-scoped listeners; cleanup |
| 3 | Application-controlled | Svelte state owned by the application; never required by a default primitive |

## Listener budget

No number in a file holds these budgets; an exact assertion does.

- **Tier 1:** the per-type listener counts for a thousand tabs roots equal those for one root, on the runtime's account and on an independent count of every `addEventListener` call (listeners.spec.ts). Live counts: [Results](https://svelte-lean.vercel.app/performance/results.md#listeners), [Proof](https://svelte-lean.vercel.app/proof.md).
- **Tier 2 (select, autocomplete):** no state for untouched roots (autocomplete.test.ts, select.test.ts); zero controllers after load for a hundred roots in Chrome, one per interacted root, released on close and on focus loss (lazy.spec.ts). Their register modules have the brotli budgets above.
