v2.1.0
Added
- Editor —
onImageUploadErrorprop, called whenonImageUploadrejects (previously upload failures were only logged to the console). Use it to surface errors to the user. - Command — Exported the
CommandGroupandCommandItemtypes from the package entry, so consumers can type thegroupsdata with full type-checking without importing from internal paths. - Command —
CommandPropsnow type-checksidanddata-*attributes on the root element (useful as a scoping/anchor target and for test/analytics hooks); previously these were rejected by the type even thoughrestPropsalready reached the root. - Input — Exported the
InputValuetype from the package entry; it is the constraint behind the publicInputProps<T>generic, so consumers can now name it directly.
Changed
-
Modal / Slideover / Drawer — BREAKING. The trigger is no longer wrapped in an extra
<button>. Previously thechildrensnippet was rendered inside the component's own trigger button, so passing a<Button>/<Link>produced an invalid nested<button>inside<button>(an SSRnode_invalid_placement_ssrhydration error that can escalate to a hardThe deferred DOM Node could not be resolvedfailure). Thechildrensnippet now receives apropsargument that you must spread onto your own focusable element, so the trigger ARIA and event handlers land on the real control. Migration:<!-- Before --> <Modal title="…"> <Button>Open</Button> </Modal> <!-- After --> <Modal title="…"> {#snippet children({ props })} <Button {...props}>Open</Button> {/snippet} </Modal>
-
Editor — Halved the serialization work per keystroke: the value-sync effect no longer re-serializes the document to compare it against a value the editor itself just emitted (it short-circuits on its own echo). Previously every edit serialized twice — once to push
value, then again in the sync effect to compare. Most noticeable foroutput="markdown"and large documents. No API or behavior change. -
Editor — The heavy optional extensions are now lazy-loaded via dynamic
import():tiptap-markdown(only whenoutput="markdown") and the table packages (only whentablesis enabled). Editors that don't use them no longer pullmarkdown-it(~80 KB gzip) orprosemirror-tables(~25 KB gzip) into the bundle — the consumer's bundler now code-splits them into separate chunks loaded on demand. Editors that enable neither still mount synchronously (no behavior change); onlymarkdown/tableseditors initialize asynchronously while their chunk loads (toolbar actions are briefly disabled andbind:apimethods are no-ops until then). -
Components — Reduced per-render styling cost: Accordion, Progress, Switch, Stepper, ThemeModeButton, and Skeleton no longer re-instantiate their
tailwind-variantsfunction on every render (defaults are precomputed/memoized and reused). No API or behavior change. -
SelectMenu — The in-dropdown search now debounces filtering (200ms) so large item lists are not re-scanned on every keystroke. Typing stays instant; only the filtered list updates after a short pause. The displayed search term and create-on-Enter are unaffected.
-
Types — Tightened several public types (no runtime change): the
data-*passthrough index signatures are nowstring | number | boolean | null | undefinedinstead ofunknown(Icon, Tabs, Pagination, Command, Select, SelectMenu, Collapsible), andSeparatorno longer intersects bits-ui'sclasstype withClassNameValue. -
Table — Internal refactor of
table.utils.ts(the default sort comparator and pin-offset computation) to bring both functions under the lint complexity limit by extracting small helpers. No API or behavior change.
Removed
- Editor — Removed the unused
toolbarGroupkey from theuislot type; it was computed but never rendered, so setting it had no effect. - Badge — Removed the internal
leadingAvatarSizekey from theuiprop type; it was accepted by the type but silently ignored at runtime (mirrors the Button fix in 2.0.0).
Fixed
- Editor — Changing the
outputprop on an already-mounted editor no longer corrupts the content.outputis read once at mount (it also decides whether the Markdown extension is loaded); it is now applied consistently to serialization, so a runtime change is simply ignored instead of producing a format/extension mismatch. Re-key the component ({#key output}) to switch formats. - Editor — The mention (
@) and slash (/) autocomplete popups are now proper ARIA listboxes: each item exposesrole="option"+aria-selected, the listbox has an accessible name, and while a popup is open the editor's contenteditable exposesaria-controls,aria-expanded, andaria-activedescendant(cleared on close) so screen readers announce the highlighted option as the user navigates. - Calendar — The Previous/Next year navigation buttons did nothing on initial render. They advanced an internal
placeholderthat stayedundefineduntil the first month navigation, so the first year click was a no-op. The placeholder is now initialized (fromvaluewhen provided, otherwise today) so year navigation works immediately. - RadioGroup — The legend is now programmatically associated with the radiogroup via
aria-labelledby, so screen readers announce the group name. Applies to the default text legend; a customlegendSlotremains the consumer's responsibility. - FileUpload — Cached blob object URLs are now revoked when the component unmounts. Previously only URLs for files removed from
valuewere revoked, so files still present at unmount leaked their object URLs.
Security
- Editor — Image URLs returned by
onImageUploadare now validated before insertion: relative URLs,http(s), and rasterdata:image/*URIs are allowed, whilejavascript:,data:text/*, anddata:image/svg+xml(an SVG script vector) are rejected (the image is not inserted and a warning is logged). Thevaluedocs now state that the HTML output is schema-validated but not sanitizer-clean — consumers must sanitize (e.g. with DOMPurify) before rendering it as raw markup elsewhere.