This release focuses on performance and bug fixes. Components and helpers were benchmarked with mitata across wall-clock speed and memory, which drove the wins on list boxes, tables, trees, tabs, and the CSS bundle. The 70 fixes cover form invalid-state accessibility, menu selectable layout, modal scrolling, combo box typeahead, list-box form serialization, pagination, keyboard behavior, and ARIA.
90 changes in this release:
| Category | Count |
|---|---|
| Breaking changes | 0 |
| Features | 0 |
| Bug fixes | 70 |
| Performance | 20 |
Full Changelog: v0.111.0...v0.111.1
Performance: benchmarks
Benchmarks use mitata to find the slow spots. Two kinds of benches:
- Pure speed. DataTable and MultiSelect sorting, virtualization math, tree helpers, fuzzy match, and similar hot paths.
- Component mount and interaction (vitest + jsdom). Wall-clock time to mount Tabs, OverflowMenu, UserAvatarGroup, TreeView, MultiSelect, and others at increasing child counts. Also times interactions like ArrowDown highlight, filter keystrokes, first expand, and menu open.
The main findings were O(n²) child registration, rebuilding Intl.Collator on every sort comparison, mounting whole trees while branches stayed collapsed, and fanning highlight state through every list-box option on ArrowDown. Each fix has a paired before/after run in the PR.
Perf PRs:
| PR | Focus |
|---|---|
| #3651 | Baseline suite; DataTable Intl.Collator sort cache
|
| #3652 | MultiSelect sort cache; lazy TreeView collapsed mounts |
| #3653 | batchStoreUpdates; Tabs, OverflowMenu, UserAvatarGroup
|
| #3654 | TabsVertical, TagSet, ProgressIndicator registration batching |
| #3657 | List-box highlight cursor; MultiSelect unmount; Pagination; DataTable paint window; ContentSwitcher |
| #3715 | CSS prune for dead bx-- selectors
|
Gains
| Path | N | Before | After | Factor | PR |
|---|---|---|---|---|---|
css/all.css gzip (minified)
| shipped bundle | 85,164 B | 83,306 B | -1.8 KB | #3715 |
css/all.css raw (minified)
| shipped bundle | 912,825 B | 898,320 B | -14.2 KB | #3715 |
| DataTable string-column sort | 10,000 rows | 131.93ms | 4.60ms | ~29x | #3651 |
| MultiSelect default item sort | 10,000 items | 1x | ~10x faster | ~10x | #3652 |
| MultiSelect mount (virtualized) | 1,000 items | 1x | ~3x faster | ~3x | #3652 |
| TreeView collapsed mount | (suite default) | 1x | ~3.5x faster | ~3.5x | #3652 |
TreeView nodes update (non-virtualized)
| 1,000 | 1x | ~120x faster | ~120x | #3652 |
| Tabs mount | 10 | 12.11ms | 2.66ms | ~4.6x | #3653 |
| Tabs mount | 80 | 1.09s | 15.76ms | ~69x | #3653 |
| Tabs mount | 100 | 1.72s | 20.33ms | ~85x | #3653 |
| OverflowMenu mount | 10 | 11.65ms | 2.44ms | ~4.8x | #3653 |
| OverflowMenu mount | 80 | 1.34s | 14.27ms | ~94x | #3653 |
| OverflowMenu mount | 100 | 2.12s | 16.88ms | ~126x | #3653 |
| UserAvatarGroup mount | 10 | 31.01ms | 2.55ms | ~12x | #3653 |
| UserAvatarGroup mount | 80 | 2.70s | 16.39ms | ~165x | #3653 |
| UserAvatarGroup mount | 100 | 4.56s | 22.83ms | ~200x | #3653 |
| TabsVertical mount | 10 | 11.97ms | 2.48ms | ~4.8x | #3654 |
| TabsVertical mount | 80 | 1.10s | 16.82ms | ~65x | #3654 |
| TabsVertical mount | 100 | 1.71s | 24.34ms | ~70x | #3654 |
| TagSet mount | 10 | 8.40ms | 2.75ms | ~3.1x | #3654 |
| TagSet mount | 80 | 74.84ms | 15.37ms | ~4.9x | #3654 |
| TagSet mount | 100 | 98.41ms | 20.00ms | ~4.9x | #3654 |
| ProgressIndicator mount | 10 | 16.28ms | 2.92ms | ~5.6x | #3654 |
| ProgressIndicator mount | 80 | 1.31s | 20.58ms | ~64x | #3654 |
| ProgressIndicator mount | 100 | 2.03s | 24.87ms | ~82x | #3654 |
| Dropdown ArrowDown highlight | 1,000 | 5.05ms | 641µs | ~8x | #3657 |
| MultiSelect closed mount | 100 | 92ms | 3.5ms | ~26x | #3657 |
| MultiSelect closed mount (no virt.) | 1,000 | 967ms | 8.6ms | ~110x | #3657 |
| ContentSwitcher mount | 10 | 10.3ms | 2.6ms | ~4.0x | #3657 |
| ContentSwitcher mount | 80 | 54.3ms | 17.5ms | ~3.1x | #3657 |
| ContentSwitcher mount | 1,000 | 1.25s | 365ms | ~3.4x | #3657 |
| Pagination mount (100k items) | pageWindow 1000 | 212ms | 103ms | ~2.1x | #3657 |
| Pagination mount (100k items) | pageWindow 100 | 26.2ms | 7.2ms | ~3.6x | #3657 |
| DataTable virtualized mount | 30k×20 | 270ms | 172ms | ~1.6x | #3657 |
| DataTable virtualized rows swap | 30k×20 | 399ms | 86ms | ~4.6x | #3657 |
Closed MultiSelect at 1,000 items also dropped heap from ~119MB to ~1.4MB (#3657). ComboBox filterMode defaults to "remove" (100-item keystroke ~3.7ms vs ~20.5ms for "hide").
What changed
- CSS. Dead
bx--selectors removed from vendored SCSS after an AST diff ofcss/all.css(#3715). Intl.Collatorcache. DataTable string sorts and MultiSelect defaultsortItemreuse one collator instead of rebuilding per comparison.- Lazy tree mount. Collapsed TreeView branches mount on first expand and stay mounted, so collapsed mount and
nodesupdates scale with visible nodes. - Batched registration. Tabs, TabsVertical, OverflowMenu, UserAvatarGroup, TagSet, ProgressIndicator, and ContentSwitcher flush child registration in one store update via
batchStoreUpdates(was O(n²) per-childstore.update()). - List-box highlight cursor. ArrowDown updates only the previous and next highlighted options instead of every item.
- Closed MultiSelect unmount. Menu DOM stays off until open, same as ComboBox. Selection still serializes through hidden inputs (see form serialization below).
- ComboBox / SearchMenu
filterMode."hide"keeps unmatched options mounted. Default"remove"is faster per keystroke. - Pagination. Page numbers render as native
<option>s. - DataTable. Under virtualization, cell records are built for the painted window only.
<script>
import { TreeView, Tabs, Tab, TabContent } from "carbon-components-svelte";
const nodes = [
{ id: 0, text: "Analytics", nodes: [{ id: 1, text: "Spark" }] },
{ id: 2, text: "Blockchain" },
];
</script>
<TreeView labelText="Products" {nodes} />
<Tabs>
<Tab label="Overview" />
<Tab label="Metrics" />
<svelte:fragment slot="content">
<TabContent>Overview</TabContent>
<TabContent>Metrics</TabContent>
</svelte:fragment>
</Tabs>Form invalid state: aria-errormessage and live alerts
Invalid fields now point at their error text with aria-errormessage and announce it with role="alert". That covers text input, text area, password input, combo box, and date picker. Date picker also links invalid, warn, and helper text through aria-describedby, and calendar day labels include the weekday.
See TextInput, ComboBox, and DatePicker.
<script>
import { TextInput, ComboBox } from "carbon-components-svelte";
</script>
<TextInput
labelText="Workspace name"
invalid
invalidText="Name is required"
/>
<ComboBox
labelText="Region"
invalid
invalidText="Select a region"
items={[
{ id: "us-east", text: "US East" },
{ id: "eu-de", text: "EU Germany" },
]}
/>Combo box: Tab commit, controlled blur, and typeahead
Keyboard-highlighted items commit on Tab. Controlled selection restores the label when blur leaves a non-matching query. Typeahead skips disabled items. Use filterMode="hide" to keep filtered options mounted (hidden) so option state survives filtering. It falls back to "remove" under virtualization.
See ComboBox typeahead and filter mode.
<script>
import { ComboBox } from "carbon-components-svelte";
let selectedId = "us-east";
</script>
<ComboBox
bind:selectedId
typeahead
filterMode="hide"
labelText="Region"
items={[
{ id: "us-east", text: "US East" },
{ id: "eu-de", text: "EU Germany", disabled: true },
]}
/>Modal: native select mouseup, stacked Escape, scrolling content
Picking an option from a native <select> inside a modal no longer closes the dialog on the orphaned mouseup. Same for composed modal. Escape closes only the topmost stacked modal. With hasScrollingContent, modals show a bottom overflow gradient and scroll newly focused elements out from under it. Popover ignores clicks on a portaled date picker calendar so nested calendars stay open.
See Modal and ComposedModal.
<script>
import { Modal, Select, SelectItem } from "carbon-components-svelte";
let open = true;
</script>
<Modal bind:open modalHeading="Export" hasScrollingContent>
<Select labelText="Format">
<SelectItem value="csv" text="CSV" />
<SelectItem value="json" text="JSON" />
</Select>
</Modal>List-box form serialization
Dropdown writes selectedId through a hidden input. Multi-select serializes selection with hidden inputs instead of the open menu DOM, so native form submit works whether the menu is open or closed. Select-all only applies to currently filtered items. Clearing the filter with the button forwards input.
<script>
import { Dropdown, MultiSelect } from "carbon-components-svelte";
</script>
<form method="post">
<Dropdown
name="region"
labelText="Region"
selectedId="us-east"
items={[
{ id: "us-east", text: "US East" },
{ id: "eu-de", text: "EU Germany" },
]}
/>
<MultiSelect
name="tags"
labelText="Tags"
items={[
{ id: "a", text: "Alpha" },
{ id: "b", text: "Beta" },
]}
/>
</form>Menu: selectable layout and keyboard
Selectable and radio items get a checkmark column separate from leading icons. If any sibling is selectable or radio, every label indents to line up. Tab closes the menu and returns focus to the anchor, same as Escape. Title tooltips sit on the interactive element. Disabled items no longer take mouse focus.
See Menu and MenuButton.
<script>
import { MenuButton, MenuItem } from "carbon-components-svelte";
</script>
<MenuButton labelText="View">
<MenuItem selectable selected>Grid</MenuItem>
<MenuItem selectable>List</MenuItem>
<MenuItem disabled>Archive</MenuItem>
</MenuButton>Pagination: labels, tooltips, focus, and narrow layouts
pageSelectLabelText customizes the page-number select label. backwardTextTooltipPosition and forwardTextTooltipPosition place the prev/next tooltips. A pageSelect slot replaces the default page picker. Below the medium breakpoint, item count and nav buttons stack. Disabled forwards to the page-size and page-number selects. When one nav button becomes disabled, focus moves to the sibling. pageSize syncs when pageSizes drops the current value. Page numbers are native options.
See Pagination.
<script>
import { Pagination } from "carbon-components-svelte";
</script>
<Pagination
totalItems={240}
pageSizes={[10, 20, 50]}
pageSelectLabelText={(total) => `Go to page, ${total} pages total`}
backwardTextTooltipPosition="bottom"
forwardTextTooltipPosition="bottom"
/>Data table and side nav
Wide tables get a horizontal scroll wrapper so sticky headers and selection columns stay usable. Side nav applies the fixed class on rail layouts so the rail does not expand on hover.
See DataTable and UIShell side navigation.
Number input: readonly, inputmode, and digit normalization
Readonly fields block arrow-key value changes. type="number" defaults inputmode to decimal for mobile keyboards. Non-Western digit glyphs normalize before parsing so pasted or IME input still resolves.
See NumberInput.
<script>
import { NumberInput } from "carbon-components-svelte";
</script>
<NumberInput labelText="Quantity" value={12} />
<NumberInput labelText="Locked total" value={100} readonly />More in this release
- Checkbox group. Invalid and warn states propagate to child checkboxes.
- Clickable tile. Space fires the click handler, not only Enter.
- Code snippet. Inline variant respects
disabled. - Data table. Horizontal scroll wrapper for wide tables. Expanded row content aligns with checkbox/radio columns. Skeleton gains the missing medium size class.
- File uploader. Invalid icon spaced from the close button.
- Link.
visitedforces visited styling. Icon wrapper is aspan. Disabled clicks are blocked. - List box. Singular clear label when exactly one item is selected.
- Multi-select. Rest props forward to the non-filterable trigger.
- Popover / toggletip. Outside-click resolves through shadow DOM. Toggletip no longer closes on browser window blur.
- Progress bar / radio button. Label-text class for truncation on progress bar and readonly radio labels.
- Progress indicator. Steps stay tabbable. Labels use spans inside buttons.
- Search. Native
type="search". Collapsed expandable input is hidden from assistive tech. - Search menu. Escape-clear no longer reopens the menu.
- Structured list. Hover highlight suppressed on selected rows.
- Tabs. Scroll buttons appear only when overflow is greater than 1px.
- Text input. Fluid
maxCountcounter sits opposite the label. - Time picker. Invalid/warn suppressed when disabled or readonly.
- Tooltip. Stops stacking and off-center caret.
- UI Shell.
HeaderPanelLinksupportsisSelected. Current-page class on nav items.HeaderPanelDividerwrapshrin anli.
What's Changed
Bug Fixes
- fix(checkbox): propagate CheckboxGroup invalid/warn to child checkboxes by @metonym in dc2a8e8
- fix(clickable-tile): fire click handler on Space, not just Enter by @metonym in 4bbdd44
- fix(code-snippet): respect disabled on the inline variant by @metonym in e56a53d
- fix(combo-box): add role=alert to invalid state error message by @metonym in ada99eb
- fix(combo-box): commit a keyboard-highlighted item on Tab by @metonym in ebfa579
- fix(combo-box): restore controlled selection label on non-matching blur by @metonym in dd4d89e
- fix(combo-box): skip disabled items in typeahead suggestions by @metonym in 5859ef7
- fix(combo-box): use aria-errormessage for invalid state by @metonym in 7b85d67
- fix(composed-modal): don't dismiss on a native select's orphaned mouseup (#3710) by @metonym in a1dec4c
- fix(composed-modal): scroll a newly-focused element out from under the scroll gradient by @metonym in a2d3ab1
- fix(data-table-skeleton): add missing medium size class (#3684) by @metonym in 91008fa
- fix(data-table): add horizontal scroll wrapper for wide tables by @metonym in 6460d97
- fix(data-table): align expanded row content with checkbox/radio columns by @metonym in 89cb52a
- fix(date-picker): add role=alert to invalid state error message by @metonym in 28b647a
- fix(date-picker): include weekday in calendar day aria-labels by @metonym in 519d3b1
- fix(date-picker): link invalid/warn/helper text via aria-describedby by @metonym in ca236b6
- fix(date-picker): use aria-errormessage for invalid state by @metonym in db1391a
- fix(dropdown): serialize selectedId via a hidden input by @metonym in 05bf2a8
- fix(file-uploader): space the invalid icon from the close button (#3716) by @metonym in ac91fb1
- fix(link): make the visited prop force visited styling (#3688) by @metonym in 107c3ac
- fix(link): use span for icon wrapper, block disabled clicks by @metonym in 10362a9
- fix(list-box): use singular clear label for exactly one selection by @metonym in 6d51a3d
- fix(menu): apply indented-content modifier to selectable/radio menu items by @metonym in b9d9aaa
- fix(menu): close and return focus to the anchor on Tab, same as Escape (#3676) by @metonym in 0ffd19a
- fix(menu): give the checkmark its own column, separate from the icon by @metonym in d6aade3
- fix(menu): indent every item's label when any sibling is selectable/radio by @metonym in 2a9c7aa
- fix(menu): move item title tooltip to the interactive element (#3677) by @metonym in 62a30b9
- fix(menu): prevent mouse focus on disabled MenuItem by @metonym in caddb21
- fix(modal):
hasScrollingContenthas bottom overflow gradient by @metonym in 3b67d47 - fix(modal): close only the topmost stacked modal on Escape by @metonym in c0a2656
- fix(modal): don't dismiss on a native select's orphaned mouseup (#3710) by @metonym in b1f95aa
- fix(modal): scroll a newly-focused element out from under the scroll gradient by @metonym in 7ab7cb8
- fix(multi-select): forward input event when filter is cleared via button by @metonym in f7f4dc6
- fix(multi-select): forward rest props to the non-filterable trigger (#3700) by @b-r-i-a-n-w-e-s-t in 0398cc1
- fix(multi-select): scope select-all to the currently filtered items by @metonym in 14a3adb
- fix(multi-select): serialize selection via hidden inputs, not the menu by @metonym in f3474ae
- fix(number-input): block arrow-key value changes when readonly by @metonym in 57c4de8
- fix(number-input): default inputmode to decimal for type=number by @metonym in ed0ce12
- fix(number-input): normalize non-Western digit glyphs before parsing by @metonym in 5c9bcac
- fix(pagination): add
pageSelectLabelTextprop (#3704) by @metonym in 71f1695 - fix(pagination): add backward/forwardTextTooltipPosition props (#3705) by @metonym in 9f0e168
- fix(pagination): add pageSelect slot for custom page-selection UI by @metonym in f0db699
- fix(pagination): forward disabled to the page-size and page-number selects by @metonym in d81ec68
- fix(pagination): refocus the sibling nav button when one becomes disabled (#3669) by @metonym in 6f95024
- fix(pagination): show item count and nav buttons below md (#3719) by @metonym in 0703214
- fix(pagination): sync pageSize when pageSizes drops the current value by @metonym in 36b1936
- fix(password-input): add role=alert to invalid state error message by @metonym in bcc6787
- fix(password-input): use aria-errormessage for invalid state by @metonym in 173f47d
- fix(popover): ignore clicks on a portaled DatePicker calendar by @metonym in eb45645
- fix(progress-bar): apply label-text class for truncation (#3717) by @metonym in 9498eae
- fix(progress-indicator): keep steps tabbable, use spans in buttons by @metonym in c954af2
- fix(radio-button): apply label-text class in readonly mode (#3718) by @metonym in fae44ed
- fix(search-menu): prevent native Escape-clear from reopening the menu by @metonym in c21f105
- fix(search): hide collapsed expandable input from assistive tech by @metonym in ed88865
- fix(search): use native type=search input by @metonym in 0037688
- fix(side-nav): apply the fixed class so rail stops expanding on hover by @metonym in 369b513
- fix(structured-list): suppress hover highlight on selected row by @metonym in 5664dd0
- fix(tabs): require >1px overflow before showing scroll buttons by @metonym in e143200
- fix(text-area): add role=alert to invalid state error message by @metonym in 7ef1967
- fix(text-area): use aria-errormessage for invalid state by @metonym in 67baa5c
- fix(text-input): position the fluid maxCount counter opposite the label (#3699) by @b-r-i-a-n-w-e-s-t in 6726370
- fix(text-input): use aria-errormessage for invalid state by @metonym in 5863c62
- fix(time-picker): suppress invalid/warn when disabled or readonly (#3670) by @metonym in 0647694
- fix(toggle-tip): resolve outside-click through shadow DOM boundaries by @metonym in 23f0267
- fix(toggletip): stop closing on browser window blur by @metonym in f07217c
- fix(tooltip): stop tooltip stacking and off-center caret (#3660) by @metonym in 38fe5d4
- fix(ui-shell):
HeaderPanelLinksupportsisSelectedprop by @metonym in 966a6e9 - fix(ui-shell): apply the current-page styling class on nav items (#3678) by @metonym in 6b4fa5d
- fix(ui-shell): wrap HeaderPanelDivider's hr in an li for valid list semantics by @metonym in afbbabc
- fix(utils): keep outsideDismiss pressed state until an explicit outside press by @metonym in 1f7b599
Performance
- perf(combo-box): add filterMode hide vs remove by @metonym in 20084a8
- perf(combo-box): apply highlight as a two-node class cursor by @metonym in c063b23
- perf(content-switcher): batch switch registration into a single store flush by @metonym in fc67705
- perf(css): prune selectors for classes the library never renders (#3715) by @metonym in 315748a
- perf(data-table): build cell records for the painted window only by @metonym in 88bfddc
- perf(data-table): cache Intl.Collator for string column sorting by @metonym in 89cbc1c
- perf(dropdown): apply highlight as a two-node class cursor by @metonym in 74a1fce
- perf(list-box): apply highlight as a two-node class cursor by @metonym in e372b9f
- perf(multi-select): apply highlight as a two-node class cursor by @metonym in 54fefdb
- perf(multi-select): cache Intl.Collator for default item sorting by @metonym in 2e790e8
- perf(multi-select): unmount the menu while closed by @metonym in e2bf986
- perf(overflow-menu): batch item registration into a single store flush by @metonym in 9942c96
- perf(pagination): render page numbers as native options by @metonym in 7ea9799
- perf(progress-indicator): batch step registration into a single store flush by @metonym in fc737f8
- perf(search-menu): keep unmatched items mounted and hidden by @metonym in f5cddbd
- perf(tabs-vertical): batch tab/content registration into a single store flush by @metonym in d7aa2be
- perf(tabs): batch tab/content registration into a single store flush by @metonym in 5428ecb
- perf(tag-set): batch tag registration into a single store flush by @metonym in bac4024
- perf(tree-view): mount collapsed subtrees lazily on first expansion by @metonym in eabe9c3
- perf(user-avatar-group): batch avatar registration into a single store flush by @metonym in 7f69d28