# Distribution

> Take what you need, ship what you use: the three consumption modes, subpath exports, the five shipping invariants with their tests, the manifest and every escape hatch.

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

Take what you need, ship what you use: installing a package, copying its styles and shipping its code are separate decisions.

## Three questions

| Question | Answer |
| --- | --- |
| What is installed? | One package per product. |
| What is copied into the application? | Nothing by default; stylesheets when the application chooses to own them. |
| What ships to production? | The registration modules of the behaviors the markup uses and the imported stylesheets. Native-only fixture: 0 B of Svelte Lean JavaScript. |

## Consumption modes

| Mode | You write | What ships |
| --- | --- | --- |
| A. Native | Platform markup; no package needed | No runtime, no registration, no import |
| B. Package-managed behavior | Markup with a static marker; the plugin or a manual import | The matching registration modules only |
| C. Owned styles | A copied stylesheet, or CSS against the protocol | Your CSS; the behavior stays a package dependency |

Behavior code is never copied: it is accessibility-sensitive, and an upstream focus fix must reach every project through npm. There is no `slean add` command yet. Setup for each mode: [Installation](https://svelte-lean.vercel.app/docs/installation.md).

`app.css`

```css
/* Mode C without any package CSS: target the protocol and the platform state directly */
[data-slean='tabs'] [role='tab'][aria-selected='true'] {
	border-bottom: 2px solid currentColor;
}

[data-slean='dialog']::backdrop {
	background: rgb(0 0 0 / 0.4);
}
```

## Package granularity

- One package per product, never one npm package per primitive.
- Behaviors are subpath exports: the plain entry holds types and pure helpers, the `register` entry is the one side effect, the root entry registers nothing.
- Table extras (grouping, pivot, editing, clipboard, export, server data, persisted state) never load unless imported.
- `sideEffects` lists only the register modules and the CSS.
- `@svelte-lean/styles` ships CSS only, one file per primitive plus tokens; its tests assert every export is a CSS file. The single-entry import is a convenience.

```ts
import type { TabsChangeDetail } from '@svelte-lean/primitives/tabs'; // types, pure helpers
import '@svelte-lean/primitives/tabs/register'; // the side effect: registers tabs once
import { BEHAVIORS } from '@svelte-lean/primitives'; // metadata and contracts; registers nothing
```

```ts
import { DataTable, createTable, createRowModel } from '@svelte-lean/table';
import { groupBy, summarize } from '@svelte-lean/table/grouping';
import { pivot } from '@svelte-lean/table/pivot';
import { createHistory, applyEdit } from '@svelte-lean/table/editing';
import { copyRange, pasteRange } from '@svelte-lean/table/clipboard';
import { toCsv, downloadText } from '@svelte-lean/table/export';
import { createDataSource } from '@svelte-lean/table/server';
import { serializeState, restoreState } from '@svelte-lean/table/state';
import '@svelte-lean/table/style.css';
```

`package.json`

```js
// packages/primitives/package.json: only the register modules are side effects
"sideEffects": ["./dist/*/register.js"]

// packages/table/package.json
"sideEffects": ["**/*.css"]
```

Exports of `@svelte-lean/primitives`, read at build time: `.`, `./button`, `./dialog`, `./popover`, `./popover/anchor/register`, `./checkbox`, `./checkbox-group`, `./checkbox-group/register`, `./radio-group`, `./tabs`, `./tabs/register`, `./menu`, `./menu/register`, `./listbox`, `./listbox/register`, `./calendar`, `./calendar/register`, `./date-picker`, `./date-picker/register`, `./input`, `./input/register`, `./field`, `./field/validation/register`, `./select`, `./select/register`, `./select/search/register`, `./select/create/register`, `./rating`, `./slider`, `./slider/register`, `./autocomplete`, `./autocomplete/register`, `./accordion`, `./card`, `./separator`, `./avatar`, `./badge`, `./kbd`, `./carousel`, `./alert`, `./progress`, `./meter`, `./skeleton`, `./breadcrumb`, `./nav`, `./pagination`, `./steps`, `./toggle`, `./toggle-group`, `./toggle-group/register`, `./toolbar`, `./toolbar/register`, `./tree`, `./tree/register`, `./toast`, `./toast/register`, `./button-group`, `./scroll-area`, `./descriptions`, `./timeline`, `./empty`, `./splitter`, `./splitter/register`, `./sortable`, `./sortable/register`, `./menu/contextmenu/register`, `./file-drop`, `./file-drop/register`, `./hover-card`, `./hover-card/register`, `./menubar`, `./menubar/register`, `./hint`, `./hint/register`, `./copy`, `./copy/register`, `./findable`, `./findable/register`, `./svelte-elements`.

Exports of `@svelte-lean/table`: `.`, `./core`, `./grouping`, `./pivot`, `./export`, `./editing`, `./state`, `./server`, `./clipboard`, `./style.css`.

## Shipping invariants

Five release-blocking invariants, tested against real production output.

| Invariant | Where it is tested | Status in this build |
| --- | --- | --- |
| A. Native-only pages ship no behavior runtime | fixtures/native-only; packages/primitives/scripts/size.mjs (native-only entry) | 34 assertions pass |
| B. Unused behaviors are absent from production output | fixtures/tabs-only, tabs-and-menu, manual-registration, table-basic | tabs-only: 48 assertions pass; tabs-and-menu: 46 assertions pass; manual-registration: 45 assertions pass; table-basic: 55 assertions pass |
| C. Instance count does not multiply shared listeners | apps/playground/tests/primitives/listeners.spec.ts (1 vs 1000 roots); packages/primitives/tests/tabs.test.ts | exact equality, release gate |
| D. Tier 2 state is lazy | packages/primitives/tests/autocomplete.test.ts and select.test.ts ("lazy controller": untouched roots hold no state, one entry per used root, released on close); apps/playground/tests/primitives/lazy.spec.ts (100 roots in Chrome) | exact assertion, release gate |
| E. Compatibility code is opt-in or target-selected | No test yet: no compatibility module exists. The first one adds a fixture proving the native path excludes it. | nothing to test |

| Fixture | Consumes | Svelte Lean chunk (brotli) |
| --- | --- | --- |
| native-only | button, dialog, popover markup; the stylesheet; the plugin | 0 B |
| tabs-only | tabs markup; the plugin | 1839 B |
| manual-registration | tabs markup; the register module imported by hand; no plugin | 1839 B |
| tabs-and-menu | tabs and menu markup; one kernel | 4600 B |
| table-basic | \<DataTable /\> and its stylesheet; six optional modules asserted absent | 20841 B |

- Each fixture is a production Vite build in its own process, with every `@svelte-lean/*` module isolated in one chunk (fixtures/README.md).
- Packed tarballs are tested separately: publint, every `exports` target, a Node import of every subpath, a server render of the table (pack-smoke.mjs).

## Manifest and report

Discovery stays inspectable: the marker is in the source, the build writes a manifest and a report, and the manual import exists. This site's manifest is on [Build-time discovery](https://svelte-lean.vercel.app/docs/architecture/build-time-discovery.md#manifest).

`vite.config.ts`

```ts
svelteLean({
	// A behavior of your own, or a replacement module for a built-in one.
	behaviors: {
		carousel: '$lib/behaviors/carousel/register',
		tabs: '@acme/primitives/tabs/register'
	}
});
```

## Escape hatches

| Abstraction | Escape hatch | Documented on |
| --- | --- | --- |
| Automatic behavior discovery by the Vite plugin | A manual import '@svelte-lean/primitives/tabs/register'; the same module, the same bytes | [/docs/installation#manual-registration](https://svelte-lean.vercel.app/docs/installation.md#manual-registration) |
| The built-in behavior table of the plugin | The behaviors option: your own registration module for a name, or false for no runtime | [/docs/architecture/build-time-discovery#options](https://svelte-lean.vercel.app/docs/architecture/build-time-discovery.md#options) |
| The document runtime | createRuntime({ root }) for a shadow root or an iframe; register the same definitions on it | [/docs/architecture/build-time-discovery#the-shared-router](https://svelte-lean.vercel.app/docs/architecture/build-time-discovery.md#the-shared-router) |
| \<DataTable /\> | createTable(): the engine it renders, for a renderer of your own; @svelte-lean/table/core for the pure row model | [/table/api](https://svelte-lean.vercel.app/table/api.md) |
| Uncontrolled table state | bind:state or onStateChange: one plain object the application owns, serializes and restores | [/table/state-persistence](https://svelte-lean.vercel.app/table/state-persistence.md) |
| The package stylesheet | Your own CSS against the protocol attributes and platform state, or a copied file you own | [/styling/owned-styles](https://svelte-lean.vercel.app/styling/owned-styles.md) |
| Native positioning of popovers | Your own anchor-name and position-anchor rules; no compatibility positioning module exists | [/primitives/popover](https://svelte-lean.vercel.app/primitives/popover.md) |
| The declarative tab selection | activateTab(root, value) and the cancelable slean:change event | [/docs/architecture/dom-protocol#events](https://svelte-lean.vercel.app/docs/architecture/dom-protocol.md#events) |

```ts
import { createTable } from '@svelte-lean/table';

// Below <DataTable />: the engine it renders, for a renderer of your own.
const table = createTable({
	data: () => rows,
	columns,
	getRowId: (row) => row.id,
	state: () => tableState,
	onStateChange: (next) => (tableState = next)
});

table.toggleSort('revenue');
table.model.rows;
```

## Source

- ADR 0005, Take what you need, ship what you use
- primitives package.json and table package.json
- fixtures/: the consumer builds and their assertions
