# Primitives

> Every primitive with its tier, measured behavior JavaScript, platform features and status, and how a primitive page is organised.

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

50 primitives in @svelte-lean/primitives: 27 are native markup with no behavior JavaScript, 17 share one listener per event type on the document, and 6 add a controller on first use.

## At a glance

A selection, live. Each name opens its page.

## What a primitive is

- Markup with a contract, not a component: no import per instance, no wrapper. The root carries `data-slean="<name>"` for the stylesheet and development diagnostics.
- **Tier 0** stops there. **Tier 1** adds one registration per page, which `@svelte-lean/vite` injects for every static marker; one shared router handles every root. **Tier 2** also keeps a small controller for a root once it is used.
- Attribute primitives ([Hint](https://svelte-lean.vercel.app/primitives/hint.md), [Copy](https://svelte-lean.vercel.app/primitives/copy.md), [Findable](https://svelte-lean.vercel.app/primitives/findable.md)) have no root of their own: any element with their attribute is one.
- Primitives with many ids to wire export an attribute helper, such as `selectParts(id)`, that writes every part id and ARIA reference from the root id; plain markup works the same. Each page shows both side by side: [Select](https://svelte-lean.vercel.app/primitives/select.md#two-ways), [Autocomplete](https://svelte-lean.vercel.app/primitives/autocomplete.md#two-ways), [Date picker](https://svelte-lean.vercel.app/primitives/date-picker.md#two-ways), [Tabs](https://svelte-lean.vercel.app/primitives/tabs.md#two-ways), [Field](https://svelte-lean.vercel.app/primitives/field.md#two-ways).
- Each contract (`contract.md` in packages/primitives/src) fixes semantics, parts, keyboard, focus, server output, behavior without JavaScript and the tests. More on [Runtime tiers](https://svelte-lean.vercel.app/docs/architecture/runtime-tiers.md) and [DOM protocol](https://svelte-lean.vercel.app/docs/architecture/dom-protocol.md).

## The primitives

| Name | Tier | Native base | Behavior JS | Shared listeners | Platform features |
| --- | --- | --- | --- | --- | --- |
| Actions |  |  |  |  |  |
| [Button](https://svelte-lean.vercel.app/primitives/button.md) | 0 · Native | `<button type="button">` | 0 B brotli | none | `<button>`, `:focus-visible`, `:disabled`, `aria-busy` |
| [Button group](https://svelte-lean.vercel.app/primitives/button-group.md) | 0 · Native | `buttons in role="group"` | 0 B brotli | none | `role="group"`, `logical radii`, `color-mix()`, `:focus-visible` |
| [Toggle](https://svelte-lean.vercel.app/primitives/toggle.md) | 1 · Delegated | `<button type="button" aria-pressed>` | not measured | click | `<button>`, `aria-pressed` |
| [Toggle group](https://svelte-lean.vercel.app/primitives/toggle-group.md) | 1 · Delegated | `<button aria-pressed> items in role="group"` | 1932 B brotli | click, keydown | `<button>`, `aria-pressed`, `role="group"`, `:dir(rtl)` |
| [Toolbar](https://svelte-lean.vercel.app/primitives/toolbar.md) | 1 · Delegated | `role="toolbar"` | 1706 B brotli | keydown, focusin | `role="toolbar"`, `aria-orientation`, `focusin`, `:dir(rtl)` |
| [Copy](https://svelte-lean.vercel.app/primitives/copy.md) | 1 · Delegated | `navigator.clipboard.writeText + <button>` | 1291 B brotli | click | `navigator.clipboard.writeText()`, `<button>`, `data-slean-copied` |
| Forms |  |  |  |  |  |
| [Input](https://svelte-lean.vercel.app/primitives/input.md) | 1 · Delegated | `<input> of any type, <textarea>; stepUp()/stepDown(), type="password"↔"text"` | 1808 B brotli | click, pointerdown, pointerover, focusin | `<input> of every type`, `<textarea>`, `stepUp() / stepDown()`, `:user-invalid`, `:placeholder-shown`, `::file-selector-button`, `field-sizing: content` |
| [Field](https://svelte-lean.vercel.app/primitives/field.md) | 0 · Native | `<label for>, a control, aria-describedby; constraint validation` | 0 B brotli | none | `<label for>`, `aria-describedby`, `aria-invalid`, `:has()` |
| [Select](https://svelte-lean.vercel.app/primitives/select.md) | 2 · Lazy | `<select> + role=combobox trigger + [popover] + ARIA listbox (role=listbox, option)` | 5022 B brotli | click, keydown, input, pointerdown, pointerover, focusout, change, toggle, reset, animationstart | `<select>`, `appearance: base-select`, `role="combobox"`, `aria-activedescendant`, `popover="manual"`, `anchor-name`, `@media (scripting)` |
| [Checkbox](https://svelte-lean.vercel.app/primitives/checkbox.md) | 0 · Native | `<input type="checkbox">, role="switch"` | 0 B brotli | none | `<input type="checkbox">`, `role="switch"`, `:checked`, `:indeterminate`, `:user-invalid` |
| [Checkbox group](https://svelte-lean.vercel.app/primitives/checkbox-group.md) | 1 · Delegated | `<fieldset> + <input type="checkbox">, indeterminate` | 1456 B brotli | change, focusin, reset | `<fieldset>`, `<input type="checkbox">`, `indeterminate`, `:indeterminate` |
| [Radio group](https://svelte-lean.vercel.app/primitives/radio-group.md) | 0 · Native | `<fieldset> + <input type="radio" name="…">` | 0 B brotli | none | `<fieldset>`, `<legend>`, `<input type="radio">`, `name` |
| [Rating](https://svelte-lean.vercel.app/primitives/rating.md) | 0 · Native | `<fieldset> of <input type="radio"> sharing a name` | 0 B brotli | none | `<input type="radio">`, `<fieldset>`, `<legend>`, `:has()`, `inline SVG` |
| [Slider](https://svelte-lean.vercel.app/primitives/slider.md) | 1 · Delegated | `<input type="range">, one or two in a group` | 1549 B brotli | input, reset | `<input type="range">`, `::-webkit-slider-thumb`, `::-moz-range-progress`, `accent-color`, `writing-mode`, `two <input type="range"> in a group` |
| [File drop](https://svelte-lean.vercel.app/primitives/file-drop.md) | 1 · Delegated | `<input type="file"> stretched over a drop area; DataTransfer for the file list` | 2983 B brotli | dragenter, dragleave, drop, paste, input, change, click, reset | `<input type="file">`, `drop`, `paste`, `DataTransfer`, `URL.createObjectURL`, `Intl.ListFormat`, `@media (scripting)` |
| [Autocomplete](https://svelte-lean.vercel.app/primitives/autocomplete.md) | 2 · Lazy | `<input list> + <datalist>; owned: role=combobox input + [popover] + ARIA listbox` | 3962 B brotli | click, keydown, input, pointerdown, pointerover, focusin, focusout, toggle | `<input list>`, `<datalist>`, `role="combobox"`, `aria-activedescendant`, `popover="manual"`, `KeyboardEvent.isComposing`, `anchor-name` |
| [Listbox](https://svelte-lean.vercel.app/primitives/listbox.md) | 1 · Delegated | `ARIA listbox markup (role=listbox, option)` | 2357 B brotli | click, keydown, change, reset | `role="listbox"`, `role="option"`, `aria-selected`, `roving tabindex`, `:dir(rtl)` |
| Date and time |  |  |  |  |  |
| [Calendar](https://svelte-lean.vercel.app/primitives/calendar.md) | 1 · Delegated | `role=grid of day buttons in a <table>` | 4594 B brotli | click, keydown, change, reset, pointerover, pointerout | `role="grid"`, `aria-selected`, `aria-current="date"`, `Intl.DateTimeFormat`, `Intl.RelativeTimeFormat`, `:dir(rtl)` |
| [Date picker](https://svelte-lean.vercel.app/primitives/date-picker.md) | 2 · Lazy | `<input type="date"> + role=combobox text input + [popover] + calendar` | 9475 B brotli | click, keydown, input, change, pointerdown, pointerover, focusin, focusout, toggle, reset | `<input type="date">`, `<input type="datetime-local">`, `<input type="time">`, `role="combobox"`, `popover="manual"`, `anchor-name`, `@media (scripting)`, `Intl.DateTimeFormat`, `Intl.RelativeTimeFormat`, `Intl.DisplayNames` |
| Overlays |  |  |  |  |  |
| [Dialog](https://svelte-lean.vercel.app/primitives/dialog.md) | 0 · Native | `<dialog> + command/commandfor` | 0 B brotli | none | `<dialog>`, `showModal()`, `command`, `commandfor`, `closedby`, `top layer`, `::backdrop`, `position: sticky` |
| [Popover](https://svelte-lean.vercel.app/primitives/popover.md) | 0 · Native | `[popover] + popovertarget or command/commandfor` | 0 B brotli | none | `popover`, `popovertarget`, `:popover-open`, `position-area` |
| [Tooltip](https://svelte-lean.vercel.app/primitives/hint.md) | 1 · Delegated | `role="tooltip" + popover="manual" + aria-describedby` | 2687 B brotli | pointerover, pointerout, pointerdown, focusin, focusout, keydown | `popover="manual"`, `role="tooltip"`, `aria-describedby`, `:focus-visible`, `Range.getBoundingClientRect()` |
| [Hover card](https://svelte-lean.vercel.app/primitives/hover-card.md) | 1 · Delegated | `popover="hint" named by interestfor` | 1986 B brotli | pointerover, pointerout, focusin, focusout, keydown | `interestfor`, `popover="hint"`, `showPopover({ source })`, `position-area` |
| [Menu](https://svelte-lean.vercel.app/primitives/menu.md) | 1 · Delegated | `[popover] + ARIA menu (role=menu, menuitem)` | 2773 B brotli | click, keydown, beforetoggle, toggle | `popover`, `beforetoggle`, `role="menu"`, `role="menuitem"`, `aria-checked`, `contextmenu event` |
| [Menubar](https://svelte-lean.vercel.app/primitives/menubar.md) | 1 · Delegated | `role="menubar" of popovertarget menu buttons` | 2064 B brotli | keydown, focusin, pointerover | `role="menubar"`, `popovertarget`, `showPopover()`, `:popover-open`, `:has()` |
| [Toast](https://svelte-lean.vercel.app/primitives/toast.md) | 2 · Lazy | `popover="manual" region + aria-live list` | 1616 B brotli | click, pointerover, pointerout, focusin, focusout | `popover="manual"`, `aria-live="polite"`, `showPopover()`, `:popover-open` |
| Navigation |  |  |  |  |  |
| [Tabs](https://svelte-lean.vercel.app/primitives/tabs.md) | 1 · Delegated | `ARIA tabs markup (role=tablist, tab, tabpanel)` | 1839 B brotli | click, keydown | `role="tablist"`, `role="tab"`, `aria-selected`, `roving tabindex`, `hidden` |
| [Nav](https://svelte-lean.vercel.app/primitives/nav.md) | 0 · Native | `<nav> + <ul> of links + <details name> groups + aria-current="page"` | 0 B brotli | none | `<nav>`, `<details name>`, `aria-current="page"`, `:has()`, `::details-content`, `scroll-target-group` |
| [Breadcrumb](https://svelte-lean.vercel.app/primitives/breadcrumb.md) | 0 · Native | `<nav> + <ol> + aria-current="page"` | 0 B brotli | none | `<nav>`, `<ol>`, `aria-current="page"`, `:dir(rtl)` |
| [Pagination](https://svelte-lean.vercel.app/primitives/pagination.md) | 0 · Native | `<nav> + links + aria-current="page"` | 0 B brotli | none | `<nav>`, `<a href>`, `aria-current="page"`, `:dir(rtl)` |
| [Steps](https://svelte-lean.vercel.app/primitives/steps.md) | 0 · Native | `<ol> + aria-current="step"` | 0 B brotli | none | `<ol>`, `aria-current="step"`, `:has()`, `CSS counters` |
| [Tree](https://svelte-lean.vercel.app/primitives/tree.md) | 1 · Delegated | `role="tree", treeitem, group, aria-expanded` | 2140 B brotli | click, keydown | `role="tree"`, `treeitem`, `group`, `aria-expanded`, `aria-selected`, `:dir(rtl)` |
| Layout and display |  |  |  |  |  |
| [Accordion](https://svelte-lean.vercel.app/primitives/accordion.md) | 0 · Native | `<details> + <summary>, exclusive groups through name` | 0 B brotli | none | `<details name>`, `<summary>`, `toggle`, `::details-content`, `interpolate-size` |
| [Card](https://svelte-lean.vercel.app/primitives/card.md) | 0 · Native | `<article>` | 0 B brotli | none | `<article>`, `::after`, `:has()` |
| [Splitter](https://svelte-lean.vercel.app/primitives/splitter.md) | 2 · Lazy | `role="separator" (WAI-ARIA window splitter) + CSS grid` | 1908 B brotli | keydown, pointerdown | `role="separator"`, `aria-valuenow`, `setPointerCapture()`, `touch-action`, `CSS grid` |
| [Sortable](https://svelte-lean.vercel.app/primitives/sortable.md) | 2 · Lazy | `<ul>/<ol> + <button> handles; document order is the value` | 3833 B brotli | pointerdown, keydown, click | `<ul>/<ol>`, `<button>`, `setPointerCapture()`, `touch-action`, `moveBefore()`, `role="status"` |
| [Scroll area](https://svelte-lean.vercel.app/primitives/scroll-area.md) | 0 · Native | `overflow: auto + tabindex="0" in role="region"` | 0 B brotli | none | `overflow: auto`, `tabindex="0"`, `scrollbar-width`, `scrollbar-color`, `scrollbar-gutter`, `overscroll-behavior`, `animation-timeline: scroll()` |
| [Separator](https://svelte-lean.vercel.app/primitives/separator.md) | 0 · Native | `<hr>, role="separator"` | 0 B brotli | none | `<hr>`, `role="separator"`, `aria-orientation` |
| [Avatar](https://svelte-lean.vercel.app/primitives/avatar.md) | 0 · Native | `<img> with initials behind it` | 0 B brotli | none | `<img>`, `alt`, `role="img"`, `CSS grid` |
| [Badge](https://svelte-lean.vercel.app/primitives/badge.md) | 0 · Native | `<span> with an optional remove <button>` | 0 B brotli | none | `<span>`, `text`, `<button>`, `aria-label`, `:has()` |
| [Keyboard key](https://svelte-lean.vercel.app/primitives/kbd.md) | 0 · Native | `<kbd>` | 0 B brotli | none | `<kbd>`, `nested <kbd>`, `:has()` |
| [Carousel](https://svelte-lean.vercel.app/primitives/carousel.md) | 0 · Native | `scroll-snap, ::scroll-button(), ::scroll-marker` | 0 B brotli | none | `scroll-snap`, `::scroll-button()`, `::scroll-marker`, `::scroll-marker-group`, `scroll-state queries` |
| [Descriptions](https://svelte-lean.vercel.app/primitives/descriptions.md) | 0 · Native | `<dl> with <div> groups of <dt> and <dd>` | 0 B brotli | none | `<dl>`, `<dt>`, `<dd>`, `CSS grid`, `min()`, `max()` |
| [Timeline](https://svelte-lean.vercel.app/primitives/timeline.md) | 0 · Native | `<ol> of <li> events with <time datetime>` | 0 B brotli | none | `<ol>`, `<time datetime>`, `:has()` |
| [Findable](https://svelte-lean.vercel.app/primitives/findable.md) | 1 · Delegated | `hidden="until-found" + beforematch` | 1887 B brotli | beforematch | `hidden="until-found"`, `beforematch`, `content: attr()`, `translate` |
| Feedback |  |  |  |  |  |
| [Alert](https://svelte-lean.vercel.app/primitives/alert.md) | 0 · Native | `<div> + live region roles (status, alert)` | 0 B brotli | none | `role="status"`, `role="alert"`, `:empty`, `color-mix()` |
| [Progress](https://svelte-lean.vercel.app/primitives/progress.md) | 0 · Native | `<progress value max>` | 0 B brotli | none | `<progress>`, `:indeterminate`, `appearance: none`, `::-webkit-progress-value`, `conic-gradient()`, `mask` |
| [Meter](https://svelte-lean.vercel.app/primitives/meter.md) | 0 · Native | `<meter min max low high optimum value>` | 0 B brotli | none | `<meter>`, `low`, `high`, `optimum`, `::-webkit-meter-optimum-value` |
| [Skeleton](https://svelte-lean.vercel.app/primitives/skeleton.md) | 0 · Native | `aria-hidden placeholders in an aria-busy region` | 0 B brotli | none | `aria-hidden`, `aria-busy`, `prefers-reduced-motion`, `:dir(rtl)` |
| [Empty state](https://svelte-lean.vercel.app/primitives/empty.md) | 0 · Native | `a heading, a paragraph and an optional action` | 0 B brotli | none | `headings`, `links and buttons`, `:empty` |

Behavior JS: the production registration module with the shared kernel (`packages/primitives/artifacts/size.json`); for Tier 0, the Svelte Lean chunk of the native-only fixture (`fixtures/results.json`). The [proof page](https://svelte-lean.vercel.app/proof.md) mounts one of each and reads the document's listeners.

## Compositions

Built from the primitives above and documented as examples, not shipped as primitives.

| Component | Composition |
| --- | --- |
| [Date range picker](https://svelte-lean.vercel.app/primitives/date-picker.md#range-picker) | a date picker in range mode |
| [Time picker](https://svelte-lean.vercel.app/primitives/input.md#types) | an input of type time |
| [Command palette](https://svelte-lean.vercel.app/primitives/autocomplete.md#command-palette) | a dialog holding an autocomplete |
| [Tags input](https://svelte-lean.vercel.app/primitives/select.md#tags-input) | a multiple select with search and create |
| [Drawer, alert dialog](https://svelte-lean.vercel.app/primitives/dialog.md#drawer) | a dialog with a side or the alertdialog role |
| [Switch, segmented control](https://svelte-lean.vercel.app/primitives/checkbox.md#switch) | a checkbox with role="switch", a segmented radio group |
| [Spinner, chip](https://svelte-lean.vercel.app/primitives/progress.md#circular) | a circular progress, a removable badge |
| [Multi-select dropdown](https://svelte-lean.vercel.app/primitives/listbox.md#multi-select-in-a-popover) | a multiple listbox in a popover |
| [Popconfirm, navigation menu](https://svelte-lean.vercel.app/primitives/popover.md#confirmation) | popovers |
| [Image preview](https://svelte-lean.vercel.app/primitives/dialog.md#lightbox) | a dialog |

Not planned as primitives: cascader, tree select and transfer (applications of tree, listbox and popover); tours, watermarks, QR codes, charts, rich text and mentions (application features). Catalog and tier decisions: docs/catalog.md.
