sveltelean Primitives
Versionv0.2.0 GitHub

At a glance

Every primitive, live, with its tier and the behavior JavaScript it adds to a page.

What a primitive is

A primitive is a documented piece of markup with a contract. The contract, a contract.md file next to the primitive's source in packages/primitives/src, fixes the required semantics, the parts, the keyboard interaction, focus rules, disabled behavior, orientation and RTL, ARIA relationships, what the server renders, what happens without JavaScript, what the package does not do, and the tests that prove each point. A typed constant next to it (contract.ts) carries the name, tier, base, parts, options and events, and is what this site reads.

A primitive is not a component. There is no import per instance and no wrapper: the markup is the primitive, marked with data-slean="<name>" for the optional stylesheet and for development diagnostics. A Tier 0 primitive stops there. A Tier 1 primitive adds one registration per page, @svelte-lean/primitives/<name>/register, which @svelte-lean/vite injects for every static marker it finds; the behavior then handles events for every root on the page through the shared router in @svelte-lean/core. A Tier 2 primitive registers the same way and, for a root that is used, keeps a small controller in a WeakMap with listeners scoped to one open session. The tiers and their budgets are on Runtime tiers; the protocol is on DOM protocol.

The primitives

NameTierNative baseBehavior JSShared listenersPlatform featuresStatus
Actions
Button0 · Native<button type="button">0 B brotlinone<button>, :focus-visible, :disabled, aria-busystable
Button group0 · Nativebuttons in role="group"0 B brotlinonerole="group", logical radii, color-mix(), :focus-visiblestable
Toggle1 · Delegated<button type="button" aria-pressed>888 B brotliclick<button>, aria-pressedstable
Toggle group1 · Delegated<button aria-pressed> items in role="group"1569 B brotliclick, keydown<button>, aria-pressed, role="group", :dir(rtl)stable
Toolbar1 · Delegatedrole="toolbar"1494 B brotlikeydown, focusinrole="toolbar", aria-orientation, focusin, :dir(rtl)stable
Forms
Input0 · Native<input type="text|email|password|search|url|tel">, <textarea>0 B brotlinone<input type="text|email|password|search|url|tel">, <textarea>, :user-invalid, :read-only, ::placeholder, field-sizing: contentstable
Field0 · Native<label for>, a control, aria-describedby0 B brotlinone<label for>, aria-describedby, aria-invalid, :has()stable
Select2 · Lazy<select> + role=combobox trigger + [popover] + ARIA listbox (role=listbox, option)3362 B brotliclick, keydown, pointerdown, pointerover, focusout, change, toggle<select>, role="combobox", aria-activedescendant, popover="manual", anchor-name, @media (scripting)stable
Checkbox0 · Native<input type="checkbox">0 B brotlinone<input type="checkbox">, :checked, :indeterminate, :user-invalidstable
Switch0 · Native<input type="checkbox" role="switch">0 B brotlinone<input type="checkbox">, role="switch", :checkedstable
Radio group0 · Native<fieldset> + <input type="radio" name="…">0 B brotlinone<fieldset>, <legend>, <input type="radio">, namestable
Segmented control0 · Native<fieldset> of <input type="radio"> sharing a name0 B brotlinone<input type="radio">, <fieldset>, <legend>, :has(), :focus-visiblestable
Rating0 · Native<fieldset> of <input type="radio"> sharing a name0 B brotlinone<input type="radio">, <fieldset>, <legend>, :has(), inline SVGstable
Slider0 · Native<input type="range">0 B brotlinone<input type="range">, ::-webkit-slider-thumb, ::-moz-range-progress, accent-color, writing-modestable
Range slider1 · Delegatedtwo <input type="range">1291 B brotliinput<input type="range">, <output for>, ::-webkit-slider-thumb, ::-moz-range-thumb, round()stable
Number field1 · Delegated<input type="number">, stepUp()/stepDown()1082 B brotliclick<input type="number">, stepUp(), stepDown(), @media (scripting), :has()stable
One-time code0 · Native<input inputmode="numeric" autocomplete="one-time-code" maxlength>0 B brotlinone<input>, inputmode="numeric", autocomplete="one-time-code", maxlength, pattern, :user-invalidstable
File field0 · Native<input type="file">0 B brotlinone<input type="file">, multiple, accept, capture, ::file-selector-buttonstable
File drop1 · Delegated<input type="file"> stretched over a drop area1107 B brotlidragenter, dragleave, drop, change<input type="file">, drop, dragenter, Intl.ListFormat, @media (scripting)stable
Color field0 · Native<input type="color">0 B brotlinone<input type="color">, ::-webkit-color-swatch, ::-moz-color-swatchstable
Autocomplete0 · Native<input list> + <datalist>0 B brotlinone<input list>, <datalist>, <option>, :user-invalidstable
Listbox1 · DelegatedARIA listbox markup (role=listbox, option)1838 B brotliclick, keydownrole="listbox", role="option", aria-selected, roving tabindex, :dir(rtl)stable
Combobox2 · Lazy<input role="combobox"> + [popover] + ARIA listbox (role=listbox, option)2564 B brotliclick, keydown, input, pointerdown, pointerover, focusout, toggle<input role="combobox">, popover, aria-activedescendant, aria-expanded, KeyboardEvent.isComposing, anchor-namestable
Date and time
Date field0 · Native<input type="date|time|datetime-local|month|week">0 B brotlinone<input type="date">, time, datetime-local, min, max, step, :user-invalidstable
Calendar1 · Delegatedrole=grid of day buttons in a <table>3925 B brotliclick, keydown, pointerover, pointeroutrole="grid", aria-selected, aria-current="date", Intl.DateTimeFormat, :dir(rtl)stable
Date picker2 · Lazy<input type="date"> + role=combobox text input + [popover] + calendar4912 B brotliclick, keydown, input, change, pointerdown, focusout, toggle<input type="date">, role="combobox", popover="manual", anchor-name, @media (scripting), Intl.DateTimeFormatstable
Overlays
Dialog0 · Native<dialog> + command/commandfor0 B brotlinone<dialog>, showModal(), command, commandfor, top layer, ::backdropstable
Alert dialog0 · Native<dialog role="alertdialog" closedby="none"> + command/commandfor0 B brotlinone<dialog>, role="alertdialog", closedby="none", command, commandfor, autofocus, returnValuestable
Drawer0 · Native<dialog closedby="any"> + command/commandfor0 B brotlinone<dialog>, showModal(), closedby="any", command, commandfor, @starting-style, translatestable
Popover0 · Native[popover] + popovertarget or command/commandfor0 B brotlinonepopover, popovertarget, :popover-open, position-areastable
Tooltip1 · Delegatedpopover="hint" + role="tooltip" + aria-describedby1297 B brotlipointerover, pointerout, focusin, focusout, keydownpopover="hint", role="tooltip", aria-describedby, showPopover({ source }), position-areastable
Hover card1 · Delegatedpopover="hint" next to its trigger1353 B brotlipointerover, pointerout, focusin, focusout, keydownpopover="hint", showPopover({ source }), position-area, pointeroverstable
Menu1 · Delegated[popover] + ARIA menu (role=menu, menuitem)1932 B brotliclick, keydown, togglepopover, role="menu", role="menuitem", aria-checked, ToggleEventstable
Context menu1 · Delegatedcontextmenu event + [popover] ARIA menu1409 B brotlicontextmenucontextmenu, showPopover(), role="menu", Shift+F10stable
Menubar1 · Delegatedrole="menubar" of popovertarget menu buttons1457 B brotlikeydown, focusin, pointeroverrole="menubar", popovertarget, showPopover(), :popover-open, :has()stable
Toast2 · Lazypopover="manual" region + aria-live list1396 B brotliclick, pointerover, pointerout, focusin, focusoutpopover="manual", aria-live="polite", showPopover(), :popover-openstable
Navigation
Tabs1 · DelegatedARIA tabs markup (role=tablist, tab, tabpanel)1521 B brotliclick, keydownrole="tablist", role="tab", aria-selected, roving tabindex, hiddenstable
Breadcrumb0 · Native<nav> + <ol> + aria-current="page"0 B brotlinone<nav>, <ol>, aria-current="page", :dir(rtl)stable
Pagination0 · Native<nav> + links + aria-current="page"0 B brotlinone<nav>, <a href>, aria-current="page", :dir(rtl)stable
Steps0 · Native<ol> + aria-current="step"0 B brotlinone<ol>, aria-current="step", :has(), CSS countersstable
Tree1 · Delegatedrole="tree", treeitem, group, aria-expanded1919 B brotliclick, keydownrole="tree", treeitem, group, aria-expanded, aria-selected, :dir(rtl)stable
Layout and display
Disclosure0 · Native<details> + <summary>0 B brotlinone<details>, <summary>, name, toggle, ::details-contentstable
Accordion0 · Native<details name> group0 B brotlinone<details name>, <summary>, toggle, ::details-content, interpolate-sizestable
Card0 · Native<article>0 B brotlinone<article>, ::after, :has()stable
Splitter2 · Lazyrole="separator" (WAI-ARIA window splitter) + CSS grid1683 B brotlikeydown, pointerdownrole="separator", aria-valuenow, setPointerCapture(), touch-action, CSS gridstable
Scroll area0 · Nativeoverflow: auto + tabindex="0" in role="region"0 B brotlinoneoverflow: auto, tabindex="0", scrollbar-width, scrollbar-color, scrollbar-gutter, overscroll-behavior, animation-timeline: scroll()stable
Separator0 · Native<hr>, role="separator"0 B brotlinone<hr>, role="separator", aria-orientationstable
Avatar0 · Native<img> with initials behind it0 B brotlinone<img>, alt, role="img", CSS gridstable
Badge0 · Native<span>0 B brotlinone<span>, textstable
Tag0 · Native<span> with an optional remove <button>0 B brotlinone<span>, <button>, aria-label, :has()stable
Keyboard key0 · Native<kbd>0 B brotlinone<kbd>, nested <kbd>, :has()stable
Carousel0 · Nativescroll-snap, ::scroll-button(), ::scroll-marker0 B brotlinonescroll-snap, ::scroll-button(), ::scroll-marker, ::scroll-marker-group, scroll-state queriesstable
Descriptions0 · Native<dl> with <div> groups of <dt> and <dd>0 B brotlinone<dl>, <dt>, <dd>, CSS grid, min(), max()stable
Timeline0 · Native<ol> of <li> events with <time datetime>0 B brotlinone<ol>, <time datetime>, :has()stable
Feedback
Alert0 · Native<div> + live region roles (status, alert)0 B brotlinonerole="status", role="alert", :empty, color-mix()stable
Progress0 · Native<progress value max>0 B brotlinone<progress>, :indeterminate, appearance: none, ::-webkit-progress-valuestable
Meter0 · Native<meter min max low high optimum value>0 B brotlinone<meter>, low, high, optimum, ::-webkit-meter-optimum-valuestable
Spinner0 · Nativerole="status" with a text label0 B brotlinonerole="status", rotate, color-mix(), prefers-reduced-motionstable
Skeleton0 · Nativearia-hidden placeholders in an aria-busy region0 B brotlinonearia-hidden, aria-busy, prefers-reduced-motion, :dir(rtl)stable
Empty state0 · Nativea heading, a paragraph and an optional action0 B brotlinoneheadings, links and buttons, :emptystable

Tier, native base and listeners come from BEHAVIORS in @svelte-lean/primitives. Behavior JavaScript is the production size of the registration module including the shared kernel (packages/primitives/artifacts/size.json); for Tier 0 primitives it is the Svelte Lean chunk of the native-only consumer fixture (fixtures/results.json). Status reads the package version, 0.2.0.

Every primitive can be inspected on the proof page, which mounts one of each and reads the listeners of the document it runs in. The contracts are in packages/primitives/src.

How to read a primitive page

Every primitive page has the same sections in the same order, so a reader learns where to look. The runtime block sits directly under the first paragraph on every page; it reads the tier, listeners and native base from the contract and the bytes from the measurement files, and says "not measured" when a file is missing.

  1. Example: the contract markup rendered live, with the same source as HTML and as a Svelte file.
  2. Why this implementation exists: what the browser owns and what Svelte Lean owns.
  3. Usage: install lines, the import or registration line, the stylesheet import, when to use it.
  4. Anatomy: the parts table from the contract.
  5. Runtime profile: how the tier and the bytes in the runtime block were established.
  6. Accessibility contract: roles, states, relationships, focus and disabled behavior.
  7. Keyboard: Key · When · Result.
  8. Platform features: Feature · Baseline · behavior outside the target.
  9. Without JavaScript, Server rendering, Before hydration: what works when, and what is asserted.
  10. Styling: the tokens the stylesheet uses, read from the file at build time, and the state selectors.
  11. Controlled integration: the events and helpers an application uses; what does not exist yet.
  12. Compatibility notes, Testing, Source: the versioned facts and the repository files.

Compositions and what is not planned

Some components other libraries ship are compositions of these primitives and are documented as examples rather than added as primitives: a date range picker is two date fields and a calendar in range mode (Date picker), a time picker is a date field of type time, a command palette is a dialog holding a combobox (Combobox), a multi-select dropdown is a listbox with data-slean-multiple in a popover (Listbox), a popconfirm and a navigation menu are popovers (Popover), an image preview is a dialog (Dialog). Cascader, tree select and transfer are applications of tree, listbox and popover; tours, watermarks, QR codes, charts, rich text and mentions are application features. The catalog and the tier decision behind every primitive are in docs/catalog.md; the checklist a new primitive must answer is in CLAUDE.md.

Names the package exports today: button, dialog, popover, disclosure, checkbox, switch, radio-group, tabs, menu, listbox, combobox, date-field, calendar, date-picker, input, field, select, segmented, rating, slider, otp-field, file-field, color-field, autocomplete, alert-dialog, drawer, accordion, card, separator, avatar, badge, tag, kbd, carousel, alert, progress, meter, spinner, skeleton, breadcrumb, pagination, steps, toggle, toggle-group, toolbar, range-slider, number-field, tree, tooltip, toast, button-group, scroll-area, descriptions, timeline, empty, splitter, context-menu, file-drop, hover-card, menubar.