Skip to content
v0.21.1

Changelog

Changelog

Unreleased

Changed

  • The showcase examples index is one flat page. Every example card shows a schematic preview image from static/examples/, and the /examples/landing, /examples/spa, and /examples/ai collection pages are removed.

0.21.1

Fixed

  • PromptBar now measures multiline wrapping against the compact input width and measures again after container resizes, which prevents layout oscillation.
  • An open PromptBar policy list now closes and blocks value changes when the composer becomes busy or disabled.
  • The PromptBar policy trigger now supports closed typeahead, and keyboard highlighting scrolls inside long policy lists.

0.21.0

Breaking

  • PromptBar replaces the native execution-policy <select> with a delegated select trigger.
  • Optional attachment, dictation, policy, and measurement controls now render only when the consumer wires their data or callbacks.
  • PromptBar replaces its two-row footer with automatic compact and expanded layouts.

Added

  • PromptBar.shape selects the rounded or pill surface shape.
  • PromptBar animates layout and surface-radius changes and disables this motion when the user prefers reduced motion.

0.20.0

Added

  • The provider-neutral @binarydreams/uikit/ai entrypoint. It exports ActivityStatus, AgentTrace, AgentResponse, ApprovalRequest, ToolCallList, AgentTaskList, Conversation, Message, PromptBar, Recommendation, ContextList, ChangeReview, and SelectionActions with their public types.
  • LiveRegion, a visually hidden ARIA live region for consumer-controlled announcements.
  • ScrubInput, a native numeric field with optional pointer dragging on its visible label, bounds, steps, suffixes, formatting, and controlled values.

Changed

  • Component API extraction and registry coverage now distinguish the package root from ./ai. AI components do not export from @binarydreams/uikit.
  • Textarea now uses a real label, stable hint and error IDs, merged aria-describedby, aria-invalid, alert errors, and a bindable native element reference.
  • Checkbox now uses the shared AccessibleName contract for a visible label, aria-label, or aria-labelledby.
  • Collapsible adds variant="plain". The existing bordered panel remains the default.
  • Table adds captions, card or scroll responsive modes, and controlled row selection with stable row identity and localized labels.
  • Toolbar actions now select icon-only, label-only, or combined presentation and a semantic tone. Toggle selection remains controlled by each group.

0.19.0

Added

  • Three named BDThemeOverrides objects: BD_PRESET_GRAPHITE_DARK, BD_PRESET_BLUEPRINT, and BD_PRESET_BLUEPRINT_DARK. Together with the Graphite Light base, they form the supported 2x2 Graphite/Blueprint and Light/Dark model. Consumers pass these objects to Provider.overrides.
  • Optional override axes for font roles, type metrics, control geometry, deep radius profiles, stroke, effects and focus, motion, and grid patterns.
  • Semantic variables for component layers, control and table recipes, panel and item radii, stroke, status foregrounds, control thumbs, and themed patterns.
  • The --bd-font-prose role for authored prose. It falls back to --bd-font-text when an override does not supply a separate stack.

Changed

  • Graphite Light is now the bare-Provider base and default. It uses zinc layers, dark neutral text, compact geometry, ledger radii, shadowless outer elevation, and interrupted execution traces.
  • Graphite Dark keeps the compact Graphite geometry on near-black layers. Blueprint Light and Blueprint Dark use roomier rounded geometry and technical-drafting palettes.
  • The showcase theme controls now select Graphite or Blueprint independently from Light, Dark, or system mode.
  • Controls now consume semantic recipe variables for type, geometry, layers, radii, stroke, focus, motion, and patterns. Components do not branch by theme ID.
  • BD_UTILITY_CSS now reads generated type and prose variables. Regenerate any hand-written :root variable block with bdBuildCssVars().

Removed

  • The previous style preset exports. The public preset surface now contains only the three objects listed above.

0.18.0

This breaking release consolidates public components around semantic contracts. No deprecated aliases remain.

Breaking

  • Meter is removed. Replace <Meter ... /> with <Progress kind="measurement" ... />.
  • MeterProps is removed. Replace it with ProgressMeasurementProps.
  • MeterTone is removed. Replace it with ProgressTone.
  • Existing task progress now requires kind. Replace <Progress ... /> with <Progress kind="task" ... />.
  • AlertDialog is removed. Replace it with <Modal kind="confirmation" ... />.
  • AlertDialogConfirmVariant is removed. Replace it with ModalConfirmVariant.
  • Existing general modals now require kind. Replace <Modal ... /> with <Modal kind="dialog" ... />.
  • DateField is removed. Replace it with <DateTimeField mode="date" ... />.
  • DatePicker is removed. Replace it with <DateTimeField mode="date-picker" ... />.
  • DateRangeField is removed. Replace it with <DateTimeField mode="date-range" ... />.
  • DateRangePicker is removed. Replace it with <DateTimeField mode="date-range-picker" ... />.
  • TimeField is removed. Replace it with <DateTimeField mode="time" ... />.
  • TimeRangeField is removed. Replace it with <DateTimeField mode="time-range" ... />.
  • ScrollArea is removed. Replace it with native overflow on the owning element.
  • Stack is removed. Replace it with local flex or grid composition and --bd-* spacing tokens.
  • Subsection is removed. Replace it with semantic HTML, local headings, and local grid composition.
  • Inset is removed. Replace it with padding on the content owner and --bd-* spacing tokens.
  • AspectRatio is removed. Replace it with native CSS aspect-ratio on the media owner.
  • Stage is removed from the package. Replace it with the showcase-only Stage from $showcase for documentation specimens.
  • StackDirection, StackGap, StackAlign, and StackJustify are removed with Stack. Replace them with local CSS values.
  • StageVariant and StageInset are removed from the package. Replace them with the showcase Stage contract.

Changed

  • DateRange, RangePreset, TimeRange, DEFAULT_PRESETS, and formatRangeDefault now export from DateTimeField.
  • NavigationMenu.openMode accepts "hover" or "click". The default is "hover"; touch activation uses clicks in both modes.
  • ContextMenu.trigger is required and delegates props to one consumer-owned target element. The component no longer adds a trigger wrapper.
  • The documentation section order is now example, accessibility, guidance, API, related content, and source. Source is direct and last.
  • Component API tables, generated Markdown, and LLM feeds now show optional prop defaults.

Fixed

  • Placeholder removes decorative guide lines. StatusDot renders two reduced-motion-safe pulse waves. Alert uses the input radius. Skeleton uses a 2.8 s shimmer. Table uses the surface background and accent-tint hover and focus treatment for data rows.

0.17.2

Removed

  • src/content/generated/ and its generators. The shiki theme is derived from the palette at load time (src/showcase/code/shiki-theme.ts), prerender entries come straight from the registries (src/content/prerender.ts), and the component API manifest is served as the virtual:bd-component-api module extracted from the library source during dev/build. docs:api, docs:api:check, and the docs:prerender:* scripts are gone.
  • The standalone scripts/docs/validate-registry.mjs and scripts/docs/validate-content.mjs validators and their docs:registry:check / docs:content:check / docs:check scripts. Their real-data checks now run inside the node:test suite (npm test).

Changed

  • Chip lg size now matches the 44 px Input height. The compact md size remains the default for filter groups.
  • Button accepts the context: ButtonContext prop. Product is the default context.
  • Button supports the rounded shape. Product Buttons now default to the rounded 12 px shape.
  • context="marketing" opts into the pill shape and primary-button glow.
  • Button size metrics remain 36 px (sm), 44 px (md), and 52 px (lg).
  • Building and tooling require Node 26 and npm 11 (engines, .nvmrc, and packageManager in package.json). svelte.config.js continues to import the registries and tokens as TypeScript natively.
  • The Yarn v1 lockfile was replaced with npm’s package-lock.json; npm now owns dependency installation and every repository command.
  • Runtime and development dependencies were refreshed to their latest compatible releases.
  • Public peer minimums are now svelte ^5.56.7 and @internationalized/date ^3.12.2.
  • TypeScript remains on 6.0.3 until the Svelte toolchain supports TypeScript 7.

0.17.0

This release stops the package from forcing the entire Phosphor set into consumer bundles, and brings the showcase up to the standard the design system documents.

Breaking

  • Icon takes a glyph component instead of a name. Replace name="House" with glyph={HouseIcon} and import the glyph yourself: import HouseIcon from 'phosphor-svelte/lib/HouseIcon';. The suffixed export is the correct one — unsuffixed aliases are deprecated in Phosphor v3.
  • IconName is removed. The generated name union is gone, and with it the namespace import that forced the whole Phosphor set into every bundle. IconGlyph (and the unchanged IconWeight) are exported from the package root in its place; a bundler now keeps only the glyphs an application imports.
  • Every icon-bearing prop takes a glyph component. Button, Chip, CopyButton, Input, RatingGroup, Tabs, AppShell, Breadcrumb, Toolbar, CommandPalette, Menu, NavigationMenu, Alert, Avatar, EmptyState, and Toast carry IconGlyph where they carried a name; Tooltip.triggerIcon follows. Pass the imported component where a string used to go.
  • Icon props no longer accept a snippet; use the paired content prop. A glyph and a snippet are both functions at runtime, so one prop cannot discriminate between them the way IconName | Snippet did. Button gains iconContent and trailingContent, Input gains leadingContent and trailingContent, and Alert gains iconContent; each takes precedence over its glyph prop. Alert.icon is now IconGlyph | booleantrue still selects the tone’s canonical glyph.

Added

  • Component documentation pages show the demo source. Each page renders the full source of the demo beside it, lazily loaded, syntax-highlighted, and copyable. The source is read from the very file that renders the demo, so the two cannot drift.
  • Every foundation has its own page. Brand, colors, typography, spacing, radius, elevation, gradients, grid backgrounds, and motion are separate registry-driven routes with an en/ru document and an optional live demo, replacing the single page that carried all nine.

Changed

  • Code samples are highlighted against a theme generated from the palette. yarn docs:api derives a full Shiki theme from BD_TOKENS, replacing createCssVariablesTheme, whose fixed ~11-token vocabulary left Svelte markup, attributes, and most identifiers uncoloured. A representative example source renders 10 distinct colours where it previously rendered 5.
  • Phosphor glyphs are imported deeply throughout. sveltePhosphorOptimize() only rewrites named barrel imports inside Vite, so tooling outside it compiled all ~1500 glyph modules. The library ships deep imports, and dist/ contains no barrel import at all.
  • Example screens are composed from kit primitives. The ten screens are rebuilt on Stack, Card, Section, Inset, Divider, and Segmented under three rules: a state is expressed once, status colour marks a boundary or a control rather than a filled region, and one region has one rhythm owner.
  • Static builds address assets from the deployment base. paths.relative is pinned to false, so a host that answers unknown paths with index.html still serves a styled page at any route depth.
  • Every yarn build emits Storybook under build/storybook/. It previously built only in yarn build:release, so deployments that ran the plain build served no /storybook/.

Removed

  • FoundationsCollection and FoundationGuidance — replaced by the per-foundation routes.
  • The --bd-shiki-* custom properties — the generated theme carries its own colours.
  • 672 orphaned message keys, left behind by the documentation and examples rebuild. Both locales stay in exact parity at 528 keys.

0.16.0

This release hardens public component contracts, accessibility, SSR output, and the documentation and release surfaces. It is not source-compatible with every 0.15.x consumer; apply the migrations below before upgrading.

Breaking

  • AppShell now requires one localized labels: AppShellLabels object. Replace navLabel, tabsLabel, openDrawerLabel, and closeDrawerLabel with primaryNavigation, mobileNavigation, openNavigation, closeNavigation, and skipToContent inside labels.
  • Meter and PinInput require an accessible name. Supply exactly one of a visible label, native aria-label, or native aria-labelledby; ambiguous and unnamed combinations are rejected by their public prop types.
  • Card.interactive is removed. Use a semantic link or button for actions instead of putting hover-only interaction styling on a generic card.
  • PinInput.ariaLabel is removed. Use the native aria-label attribute when there is no visible label.
  • Swatch moved to the showcase layer and is no longer exported from the package. Replace package imports with the showcase-only Swatch where documenting tokens; consumer applications should render their own token presentation.

Added

  • Public contracts are exported for hardened components. AccessibleName, InputProps, MeterProps, PinInputProps, SelectProps, AppShellProps, and AppShellLabels are available from the package root.
  • Card keeps fixed tokenized padding container-safe. Its border box, minimum inline size, and maximum inline size preserve the existing spacing recipes inside narrow containers.
  • Meter.valueLabel exposes human-readable values. The visible value and meter semantics now share the same normalized range.
  • PinInput is form-native and SSR-safe. It forwards native input and form attributes to one stable control, provides a no-JavaScript fallback, and preserves exact length validation after hydration.
  • Table.rowKey provides stable row identity. Consumers can supply a primitive key selector; object identity remains the fallback.
  • Canonical release commands are part of the repository contract. yarn dev is the primary development command, and yarn build:release produces and validates the site, package, and Storybook under build/storybook/.

Changed

  • Input preserves external label composition. Pair a public Label for with the matching Input id, or supply at most one internal label, native aria-label, or native aria-labelledby; the public type no longer rejects the valid external-label shape.
  • AppShell mobile navigation is a modal Bits UI dialog. It uses the Provider portal boundary, manages focus and restoration, closes at the canonical desktop breakpoint, includes a skip link, and keeps bottom navigation semantic.
  • Overlay triggers delegate to consumer-owned elements. Modal and AlertDialog pass trigger props through their snippets instead of adding nested interactive wrappers.
  • CommandPalette item identity is independent of visible titles. Duplicate translated titles no longer collide.
  • Scoped Provider utility styles are fully isolated. Utility selectors remain inside the scope or shadow root while document-mode CSS stays unchanged.
  • Icon documents and exposes the complete Phosphor glyph contract. Dynamic names continue to resolve against the full upstream set.
  • Success and warning semantic tones meet WCAG AA. Both support white text on solid fills and tone text on their 12% surface tints.

Fixed

  • Disabled or loading link-shaped Buttons cannot navigate or activate. Their semantics, keyboard behavior, and pointer behavior now match disabled buttons.
  • Accordion preserves correct focus and presence behavior. Closed panel descendants unmount, and caret and panel motion respect reduced-motion preferences.
  • Select composes native ARIA descriptions consistently. Consumer aria-describedby is preserved alongside the single current feedback ID; an error replaces the hint and forces aria-invalid="true".
  • The site emits the complete default theme and utility CSS during SSR. Header geometry is styled before hydration, removing the first-load layout flash.
  • Documentation and release surfaces are synchronized. Generated API data resolves imported public prop aliases, component guidance and demos remain paired in English and Russian, all public components have sorted Storybook stories, and the site links to the base-aware Storybook artifact.

0.15.2

A mobile-input hardening release from the consumer-app mobile audit: no new APIs, three fixes.

Fixed

  • Input text is 16px, ending iOS Safari’s zoom-on-focus. The native <input> was set in 15px, and iOS Safari auto-zooms the whole page (and leaves it zoomed) whenever a control with a computed font-size under 16px receives focus — every text field in every consumer app, on every visit, at every phone width. The bump is flat (no media query): 1px is visually negligible next to keeping desktop and touch renders identical. Consumers should not counter it with maximum-scale=1 — that suppresses pinch-zoom on some Android browsers and fails accessibility audits.
  • Input’s reveal toggle has a real touch target. The show/hide-password button was content-sized to its 18px icon — under even the WCAG 2.5.8 AA 24px minimum, and sitting inside the competing 44px input target, so a thumb tap usually focused the field (popping the keyboard) instead of toggling. The button now stretches to the field height with an explicit width — a 36×38px hit area — while a negative right margin eats into the field’s padding so the icon stays within 1px of where it drew before. Its focus ring drops outline-offset to 0 so the ring stays inside the field border instead of poking past it.
  • Button grows with a wrapping label. The size recipe pinned an exact height, so a label that wrapped to three or more lines (long localized strings on narrow phones) painted outside the pill into neighboring content. The recipe now sets min-height: single-line and icon-only buttons render pixel-identical to 0.15.1 (icon-only stays square — its width rule is separate), and only a multiline label grows the pill to contain itself. Note that buttons in a stretch-aligned flex row now equalize to the tallest sibling, which is the wanted outcome in the kit’s own dialog footers.

0.15.1

Fixed

  • PinInput cells shrink on narrow containers. The six 46px cells plus 10px gaps put the row at a fixed 326px — wider than the content area of a phone-width card (e.g. ~262px inside a padding="lg" Card at 390px viewport), so the row forced a horizontal scroll. Cells now carry flex: 0 1 46px; min-width: 0 and shrink evenly to fit; at rest the row is pixel-identical to 0.15.0.
  • Document-mode base rule resets the UA body margin. BD_UTILITY_CSS / the Provider’s document-mode injection now emit body { margin: 0; … }. The browser-default 8px margin put a halo around AppShell’s 100dvh grid — a permanent 16px document scroll plus 16px of lost width on every screen. The showcase’s own app.html always carried this reset; consumers got nothing. The scoped (.bd-scope) rule is unchanged — stripping margins off an arbitrary host element is not the kit’s call.

0.15.0

An accessibility and SSR-ergonomics release driven by the first consumer apps: heading levels that fit a real document outline, a <main> landmark, localizable shell labels, OTP autofill, a visible Switch label, an AA-compliant muted text token, and the utility stylesheet as a public export. Additive — no renames, no removed props.

Added

  • Subsection.headingLevel (16, default 3). The title used to be a hardcoded <h3>, forcing an h1→h3 skip in any app whose shell already carries the <h1>. The style is now on a .bd-subsection-title class, so every level looks identical. Adopters: pass headingLevel={2} when the subsection sits directly under a page <h1> (e.g. inside AppShell with an <h1> in the top bar).
  • AppShell renders the content card as a <main> landmark. Screen-reader users previously got no main region from the shell at all. New mainLandmark?: boolean (default true) opts out for the embedded case — a shell mounted inside a page that already has its own <main> (a document must carry exactly one). Adopters: drop any <main> wrapper you were rendering inside the shell’s content slot — it would now be nested and invalid.
  • Switch.label — a visible caption wired to the control through a real <label for> (bits-ui Label.Root), so clicking the text toggles the switch, mirroring Checkbox’s composition and sizing (13/14/15px across sm/md/lg). ariaLabel is unchanged and stays the right tool for icon-only placements; with no label the rendered DOM is byte-identical to 0.14.0. (0.14.0 renamed the old aria-only label to ariaLabel; this label is a new, visible thing.)
  • PinInput labeling + ariaLabel. label now renders as a true <label for> bound to the hidden <input> bits-ui overlays across the cells (it was a bare, unassociated <span>), new ariaLabel names the input when no visible label is wanted, the error line is wired via aria-describedby/aria-invalid, and the presentational cell row is aria-hidden so the code isn’t announced twice.
  • BD_UTILITY_CSS / BD_UTILITY_CSS_ID — the exact document-mode utility stylesheet the Provider injects (body base rule + .bd-* type/gradient/grid classes) and the id of its <style> element, exported from the package root. SSR apps inline <style id={BD_UTILITY_CSS_ID}>{BD_UTILITY_CSS}</style> in <head> for a themed first paint (no FOUC) instead of hand-mirroring the classes; the Provider sees the id on mount and skips its own injection. The Provider’s runtime behavior is unchanged.

Changed

  • --bd-text-muted now meets WCAG AA. rgba(14, 42, 102, 0.62) composited to ~4.25:1 on white — just under the 4.5:1 minimum for the 11–13px copy the kit sets in it. The alpha is now 0.68: 5.09:1 on surface, 4.95:1 on surface-alt, 4.83:1 on canvas, 4.63:1 on sunken. Muted copy across the kit gets very slightly darker; the muted↔text hierarchy is preserved. Review before adopting only if you fine-tuned custom UI against the old value.
  • AppShell.tabsLabel default no longer glues an English suffix onto navLabel. It was `${navLabel} (compact)`, which broke localization for any non-English navLabel; it now falls back to navLabel as-is. Localized apps: navLabel, openDrawerLabel, closeDrawerLabel, and tabsLabel all default to English strings — pass localized values for all of them; give tabsLabel a distinct string when the two nav landmarks should be told apart.

Fixed

  • PinInput pins autocomplete="one-time-code" on its input explicitly (iOS SMS/email code autofill). bits-ui already defaults to it, so this is a guarantee rather than a behavior change — it’s now part of this component’s contract rather than an upstream default.

0.14.0

A breaking API-naming release. No new components and no behavior change beyond the fixes listed under Added — this pass converges prop and type names across the kit onto consistent vocabulary (variant for visual recipes, description for the secondary-copy slot, ariaLabel for aria-only strings, value/checked/active matched to the right bindable per control). There are no aliases or deprecated shims: update call sites to the new names.

Breaking — core / tokens

  • BD_TOKENS.bpBD_TOKENS.breakpoint. The breakpoints map only; the unrelated --bd-bp-* blueprint CSS variables are untouched.
  • type DeepPartialBDDeepPartial, exported from src/lib/index.ts under the new name.
  • BDDensity fields renamed: rowHrowHeight, padYpaddingY, padXpaddingX. The CSS variables they feed (--bd-row-h, --bd-pad-y, --bd-pad-x) are unchanged.
  • BDAccent loses its dead note field. It carried no runtime behavior; the type and the BD_ACCENT literal both drop it.
  • internal/boundary.ts: BDBoundary.rootscopeRoot, including every overlay’s getBoundary()?.root() call site.
  • Provider: rootscoped, semantics inverted. root?: boolean (default true) is now scoped?: boolean (default false); scoped={true} is the old root={false}. The accent and theme shorthand props are gone — overrides?: BDThemeOverrides is the single entry point for per-instance recoloring. Provider root={false}Provider scoped; Provider accent={X}Provider overrides={{ accent: X }}; Provider theme={Y}Provider overrides={{ theme: Y }}.

Breaking — controls

  • Swatch: namelabel, roletag, fgtagColor.
  • Link: tonevariant ('accent' | 'muted' values unchanged).
  • LogoStrip: nameslabels; type LogoStripToneLogoStripVariant (prop tonevariant).
  • Avatar: tonevariant; type AvatarToneAvatarVariant; the 'cream' literal is now 'sunken'. size?: number widens to size?: number | ControlSize (sm=32, md=40, lg=56 px; raw numbers still work).
  • Alert: icon?: IconName | Snippet | trueicon?: IconName | Snippet | boolean.
  • CodeBlock: language?: CodeLang | stringCodeLang | (string & {}) (same runtime type, better autocomplete).
  • Stat: eyebrow?: Snippetleading?: Snippet.
  • EmptyState: bodydescription.
  • Meter: MeterTone is now exported from a new MeterTypes.ts as export type MeterTone = 'accent' | 'success' | 'warning' | 'danger'.
  • Button: fullfullWidth.
  • Checkbox: value/onValueChangechecked/onCheckedChange (still bindable).
  • Switch: value/onValueChangechecked/onCheckedChange; labelariaLabel (it was always aria-only).
  • Chip: value/onValueChangeactive/onActiveChange.
  • Segmented: labelariaLabel.
  • Tabs: labelariaLabel; type TabTabItem.
  • Textarea: countshowCount.
  • Input: iconleading (pairs with the existing trailing).
  • CopyButton: successTone?: booleantone?: 'neutral' | 'success' (old true maps to 'success').
  • Accordion: AccordionItem.idvalue (matches Tab/RadioOption/SelectOption and bits-ui itself).
  • Calendar: public prop placeholderviewDate (still forwarded to bits-ui’s bind:placeholder internally).
  • PinInput: type: 'numeric' | 'alphanumeric' | 'text'characters: 'numeric' | 'alphanumeric' | 'any''text' becomes 'any'.
  • FileUpload: FileUploadError’s type: 'size' | 'type'reason: 'maxSize' | 'accept'.
  • DateRangePicker: min/maxminValue/maxValue; value is now optional and bindable, defaulting to { start: undefined, end: undefined }.
  • Import renames: TabTabItem.

Breaking — layout

  • Card: tonevariant; type CardToneCardVariant.
  • Stage: tonevariant; notedescription; minHminHeight; inset?: boolean | 'center' | 'flushX' | 'flushY'inset?: StageInset ('none' | 'plain' | 'center' | 'flushX' | 'flushY', default 'none'; the old bare true is now 'plain'). New StageTypes.ts exports StageVariant and StageInset.
  • Section: kickerdescription.
  • Subsection: notedescription.
  • ScrollArea: maxHeight?: numbernumber | string (number = px, string = raw CSS length); frameframed.
  • Toolbar: ToolbarToggleGroup items now use an exported type ToolbarToggleItem = { value: string; label: string; icon?: IconName } from ToolbarTypes.ts.
  • Breadcrumb: type CrumbBreadcrumbItem; labelnavLabel; separator and separatorSnippet merge into a single separator?: BreadcrumbSeparator | Snippet. New overflowLabel?: string (default 'Show hidden crumbs'), wired to the overflow Menu’s triggerLabel.
  • AppShell: AppNavItem.activecurrent (still renders aria-current="page"). New openDrawerLabel?: string (default 'Open navigation') and closeDrawerLabel?: string (default 'Close navigation') replace the two hardcoded aria-labels on the mobile drawer; new tabsLabel?: string (default `${navLabel} (compact)`) labels the mobile tab bar. searchTrigger literals (including 'topbar') are unchanged.
  • Divider: label and vertical are now mutually exclusive at the type level — supplying label with orientation="vertical" is a type error.
  • Import renames: CrumbBreadcrumbItem.

Breaking — overlays

  • Modal: confirmToneconfirmVariant; type ModalConfirmToneModalConfirmVariant. New export type ModalSize = 'sm' | 'md' | 'lg' in ModalTypes.ts, now used to type the existing size prop.
  • AlertDialog: toneconfirmVariant (type AlertDialogToneAlertDialogConfirmVariant); actionLabelconfirmLabel; onActiononConfirm — this converges the dialog’s vocabulary onto Modal‘s.
  • CommandPalette: type CmdItemCommandPaletteItem; type CmdGroupCommandPaletteGroup. Item shape (icon/image/kbd/meta) is unchanged.
  • NavigationMenu: type NavMenuLinkNavigationMenuLink; type NavMenuEntryNavigationMenuEntry.
  • Toast: the toast.error alias is gone — toast.danger is canonical. ToastData.action.onClickonSelect.
  • Tooltip: exactly one of trigger (snippet) or triggerIcon (IconName) is now required, enforced by a discriminated union — supplying both, or neither, is a type error.
  • Import renames: CmdItem/CmdGroupCommandPaletteItem/CommandPaletteGroup, NavMenuLink/NavMenuEntryNavigationMenuLink/NavigationMenuEntry, ModalConfirmToneModalConfirmVariant, AlertDialogToneAlertDialogConfirmVariant.

Breaking — data / charts

  • Table: type ColumnTableColumn; TableSort.dirdirection; onSort?: (key, sort) => voidonSortByChange?: (sort: TableSort) => void (the redundant key argument is gone — read it off sort instead).
  • Pagination: showEdgesshowFirstLast.
  • Chart: kindvariant (type ChartKindChartVariant); legendshowLegend; tooltipshowTooltip (tooltipContent is unchanged).
  • Sparkline: kindvariant. Every exported Spark*-prefixed type is now Sparkline* (SparkKindSparklineVariant, etc.). The previously-internal _shared curve type is now a public export type SparklineCurve = 'linear' | 'monotone' in SparklineTypes.ts, and the curve prop is typed with it.
  • Import renames: ColumnTableColumn, ChartKindChartVariant, Spark*Sparkline*.

Added — behavior fixes bundled with the renames

  • Avatar a11y fix. New statusLabel?: string prop. The accessible status text used to be hardcoded to “Online”/“Offline” regardless of the actual status value; it now auto-derives only for status="online" / status="offline", uses statusLabel for the other five status values, and omits the aria text entirely when neither applies.
  • Accordion.headingLevel now does something. The heading level was accepted as a prop but the component always rendered an <h3>; it now renders svelte:element this={'h' + (it.headingLevel ?? 3)}, so per-item heading levels actually take effect.
  • FileUpload.files is bindable. The selection used to be private internal state, readable only through onFilesChange; it’s now files = $bindable([]), so callers can both read and seed the selection. onFilesChange still fires on every change.
  • DateRangePicker.label is now visible. It used to be aria-only; it now renders a caption above the trigger (mirroring DateField’s label markup, .bd-drp-label) while keeping the accessible name wired to the trigger.
  • Select.hint. New hint?: string prop, rendered like Input’s hint/error line below the trigger (error wins when both are set).

0.13.0

Provider is now first-class Shadow-DOM-safe, so the kit can mount inside a shadow root — an embeddable widget on a page you don’t control — without touching the host document. Backward-compatible: document mode (root={true}, the default) is unchanged.

Added

  • Shadow-DOM boundary. Provider root={false} now injects the utility styles into the correct root — an adopted stylesheet when it sits inside a ShadowRoot, otherwise a scoped #bd-kit-utilities-scoped <style> in <head> — scopes the base background/color/font rule to its own .bd-scope wrapper instead of body, and never mutates document.documentElement or body. It also renders an in-scope .bd-portal-layer and publishes a portal boundary through Svelte context (src/lib/internal/boundary.ts).
  • Every overlay that portals a floating panel — Select, DatePicker, Popover, Menu, Tooltip, Modal, Sheet, AlertDialog, ContextMenu, Menubar, LinkPreview, CommandPalette — now portals into that boundary when a scoped Provider is present, so the panel stays inside the shadow tree. With no scoped Provider (document mode) they portal to document.body exactly as before.
  • Spacing proximity ramp (DESIGN.md §12). Codified the vertical-rhythm doctrine: a Tight → Grouped → Related → Distinct → Zone ramp mapping how related two blocks are to a --bd-s-* gap, a 40px whitespace ceiling, and a “one spacing owner” rule.
  • Stack gains a 2xl (40px) gap — the Zone tier of the proximity ramp.
  • Subsection gains a flush prop — drops its own bottom margin so a parent rhythm container owns the vertical spacing. The default keeps the current 48/32px margin, so existing layouts are unchanged.
  • The package now ships DESIGN.md and CHANGELOG.md so the design-system rules travel with the install — consumers get the usage contract without opening the repository.

Changed

  • .bd-h1.bd-h6 utility classes now reset margin to 0. Semantic headings (<h1 class="bd-h5">) no longer drag the browser’s default heading margin into a flex/grid rhythm; the type utilities own font, not spacing.
  • Otherwise nothing breaking. Provider root={true} (document mode, the default) behaves byte-for-byte as in 0.12.0, and Subsection’s default spacing is unchanged.

0.12.0

Every component bits-ui ships now has a kit counterpart. 18 new components, four internal rebases with unchanged public props, and a new /components showcase route.

Added

  • Label (primitives/) — native <label> (bits-ui Label.Root) sized to ControlSize, with a required asterisk marker.
  • Meter (presentational/) — static measurement bar (bits-ui Meter, role="meter") for a known range — battery, storage — never a task in flight, which is what Progress is for.
  • Collapsible (interactive/) — single expand/collapse panel; the panel height animates from bits-ui’s real measured --bits-collapsible-content-height, never a guessed max-height.
  • RatingGroup (interactive/) — glyph rating with one roving role="slider" tab stop; arrow keys set the value, allowHalf for half-glyph precision.
  • Calendar (interactive/) — single-date grid, sibling to the range grid DateRangePicker already wraps.
  • DateField, DatePicker, DateRangeField, TimeField, TimeRangeField (interactive/) — the rest of the segmented date/time family: text inputs built from one focusable Segment per part, DatePicker adding a Calendar grid in a floating panel. All take and return DateValue/TimeValue/DateRange/TimeRange from @internationalized/date / bits-ui, never Date.
  • AspectRatio (layout/) — fixed width÷height frame for embeds and media.
  • ScrollArea (layout/) — styled scroll container with a slim custom bar in place of the native one.
  • Toolbar (layout/) — data-driven action row (button/link/separator/toggle-group items) with bits-ui’s roving focus, matching Menu’s declarative item-array model.
  • AlertDialog (overlays/) — forced-choice confirm (role="alertdialog"); unlike Modal, it never dismisses on Escape or an outside click.
  • ContextMenu (overlays/) — right-click action menu over Menu’s declarative MenuItem[] model and panel classes.
  • LinkPreview (overlays/) — hover/focus preview card for a link; never opens on touch.
  • Menubar (overlays/) — desktop-style File/Edit/View bar; once one menu is open, arrow keys move between the others.
  • NavigationMenu (overlays/) — top nav with hover/focus-opened panels and a sliding indicator under the active trigger.
  • Button.href — render as an <a> instead of a <button>; bits-ui swaps the element and its disabled semantics for us. See Changed below.
  • /components route — a five-section catalogue (inputs, dates and time, navigation, feedback, layout) for every component above plus Label/RatingGroup/Collapsible/Meter/AspectRatio/ScrollArea/AlertDialog, all of which had nowhere to live before.

Changed

  • Button, Avatar, Divider, Segmented now sit on bits-ui. Public props are unchanged for all four (Button gains the additive href above). Button delegates onto bits-ui’s Button.Root, which renders <button> or <a> and owns the disabled semantics of each. Avatar delegates onto bits-ui’s Avatar (Root/Image/Fallback), which tracks a real image loadingStatus instead of our old onerror guess. Divider delegates onto bits-ui’s Separator. Segmented delegates onto bits-ui’s ToggleGroup type="single" instead of Tabs — the correct semantics for a group of toggles, not a tablist — so external selectors keyed on role="tab" will no longer match a Segmented option; its look, props, and keyboard model are otherwise unchanged.
  • DateRangePicker moved from /charts to /components. It’s an input, not a chart; it only sat there because it shipped alongside Chart. The ch-daterange section and its nav entry are gone from /charts; the picker now lives in /components’ “Dates and time” section alongside its new siblings.
  • Nav reordered. Foundations → Components → Charts → Mobile → Landing → App landing → SPA → Guidelines. Charts moves up from the bottom to sit with the other building blocks, ahead of the scenario pages.

0.11.0

Four new components and one additive Popover API. No breaking changes.

Added

  • CodeBlock (presentational/) — read-only snippet surface with a copy control. No highlighter is bundled; highlight is an opt-in callback whose return value is injected with {@html}, so the caller owns sanitisation.
  • Pagination (data/) — controlled page controls over bits-ui’s Pagination, which owns the page math, the ellipsis items, and the item range behind the 1–50 of 1,234 summary. A standalone sibling of Table.
  • Breadcrumb (layout/) — nav > ol trail; past maxItems the middle collapses into a Menu.
  • Tabs (interactive/) — panelled tabs with underline and pill variants. Segmented remains the compact, panel-less sibling.
  • Popover.anchor — position the panel against any Measurable ({ getBoundingClientRect() }) instead of a trigger. trigger is now optional; an anchored popover renders none.
  • Popover.focusOnOpen — pass false to leave focus where it is, for a mention menu over a textarea. The panel is then never focused, so the caller owns the combobox ARIA on the input side.
  • Measurable type, exported from the package. bits-ui does not export its own.

0.10.0

The headless layer moves from melt to bits-ui.

melt (next-gen) shipped no release after 2026-01-04 and no library commit after 2026-03-04. bits-ui is the maintained successor from the same author, with a wider component surface. Every component’s public props survive the move except where noted below: the DOM, the bd-* class names, and the --bd-* tokens are unchanged, because our element is delegated through bits-ui’s child snippet rather than rendered by it.

Breaking

  • Tree is removed. Tree, TreeNode, and NodeState are no longer exported. It was the last melt-only component with no bits-ui counterpart and no external consumers.
  • DateRangePicker moves to DateValue. DateRange is now { start: DateValue | undefined; end: DateValue | undefined } from @internationalized/date, replacing { start: Date; end: Date }. Both ends became optional because the calendar reports a half-open range while the user is mid-selection — code reading range.start must now handle undefined. RangePreset.getRange returns the same shape.
  • peerDependencies tightened. svelte is now ^5.33.0, the floor bits-ui sets. @internationalized/date ^3.8.1 is a new peer: DateValue appears in the public type surface, so the consumer must resolve the same package instance the kit does. Install it if you use DateRangePicker.
  • PinInputError moved. It used to be re-exported from melt/builders; it now ships from the kit’s own types.

Changed behaviour, unchanged signatures

  • CommandPalette. With the default filter={true}, results are now ranked by a relevance score rather than filtered by case-insensitive substring, so the order of results differs. filter={false} and a predicate filter keep their exact previous semantics.
  • Menu. It was a Popover with role="menu" painted on by hand and a hand-rolled roving cursor. It is now a real menu with typeahead, submenu support, and managed focus. External selectors that relied on the old DOM will not match.
  • Modal, Sheet, CommandPalette. These rendered a native <dialog> driven by showModal(), with the backdrop pushed into the top layer via the Popover API. They now render bits-ui’s portalled role="dialog" panel, which owns the focus trap, the scroll lock, Escape, and outside-click. Leaving the top layer means the backdrop and the panel position themselves with z-index.
  • Overlay animation. bits-ui mounts content already open and waits for a CSS animation before unmounting, so every overlay’s enter/exit is now a @keyframes animation keyed on data-state="open" | "closed" instead of a transition on [data-open].
  • Floating panels moved onto the modal stacking plane. melt rendered a popover’s content inline, so a Select inside a Modal inherited the modal’s stacking context. bits-ui portals it to <body>, where the old --bd-z-dropdown (100) sat far below --bd-z-modal (900) — the panel rendered behind the dialog that owned it. Popover, Menu, Select, and Tooltip now use a new --bd-z-floating token, which shares the modal’s value; portal mount order settles the rest. Modal’s and CommandPalette’s backdrops moved from --bd-z-overlay to --bd-z-modal so a dialog opened from a Sheet dims it.

Added

  • --bd-z-floating token. See above. BD_TOKENS.z gains a floating entry.
  • Checkbox supports indeterminate. A bindable tri-state prop reporting aria-checked="mixed". The component is no longer a native <input type="checkbox">.
  • Switch reports role="switch". The old Toggle builder never rendered it.
  • Toaster gained a label prop for the accessible name of the toast region.
  • DateRangePicker’s default caption survives mid-selection. With a start picked and an end pending it reads 12 Mar – … instead of falling back to the placeholder.

Internal

  • Toaster and FileUpload are hand-rolled: bits-ui ships neither. The toaster owns its queue, its pause-on-hover countdown (which resumes from the remaining time rather than restarting), and the aria-live politeness split — assertive for danger and warning, polite otherwise. FileUpload owns the hidden input, the accept/maxSize validation, and the selection list.
  • DateRangePicker sheds 537 lines of hand-written calendar logic and the 123-line date.ts. Locale-correct calendars, isDateUnavailable, and the keyboard contract now come from bits-ui’s RangeCalendar.
  • Hand-rolled keyboard handling now survives in exactly two places, both for want of a bits-ui counterpart: AppShell’s Escape-closes-the-drawer, and FileUpload’s Enter/Space-opens-the-picker.