github carbon-design-system/carbon-components-svelte v0.108.0

4 hours ago

This release comes from a comprehensive audit of the library: closer parity with the upstream Carbon library, bug fixes, accessibility work in forms, menus, tables, and the shell, performance wins on list filtering and virtualized menus, plus new features (RangeSlider, readonly on most form controls, async copy) and quality-of-life improvements across dozens of components.

218 changes in this release:

Category Count
Breaking changes 5
Features 54
Bug fixes 148
Performance 11

Full Changelog: v0.107.1...v0.108.0


readonly on form controls

Most inputs and pickers now support readonly. Use it for review-only or permission-gated UIs. Unlike disabled, the value stays focusable and submits with the form. Invalid and warn styling is turned off while readonly.

Works on ComboBox, Dropdown, MultiSelect, Select, TextArea, TextInput, NumberInput, PasswordInput, Checkbox / CheckboxGroup, RadioButton / RadioButtonGroup, Toggle, DatePickerInput, TimePicker / TimePickerSelect, and other list-box-backed controls.

Readonly form controls

RangeSlider: dual-handle range selection

New component for a lower and upper bound. Bind value and valueUpper. Same states as Slider: disabled, readonly, invalid, warn, skeleton.

See the Range slider docs.

<script>
  import { RangeSlider } from "carbon-components-svelte";

  let min = 10;
  let max = 90;
</script>

<RangeSlider
  labelText="Runtime memory (MB)"
  min={10}
  max={990}
  step={10}
  bind:value={min}
  bind:valueUpper={max}
/>
range-slider.mov

CopyButton and CodeSnippet: async copy and custom feedback icon

Pass an async copy function when the clipboard text is not ready at render time (fetched install commands, generated snippets, etc.). While the request runs, the control sets aria-busy="true" and ignores repeat clicks. A rejected copy emits copy:error and skips the success message.

Prefetch on hover with on:mouseenter on CopyButton, or on:mouseenter:copy-button on CodeSnippet. Set feedbackIcon to swap the copy icon during the success window.

See CopyButton async copy, CopyButton feedback icon, CodeSnippet async copy, and CodeSnippet feedback icon.

<script>
  import { CopyButton } from "carbon-components-svelte";
  import Checkmark from "carbon-icons-svelte/lib/Checkmark.svelte";

  let cachedCommand = null;

  async function copyInstallCommand() {
    if (!cachedCommand) {
      cachedCommand = await fetchInstallCommand();
    }
    await navigator.clipboard.writeText(cachedCommand);
  }
</script>

<CopyButton
  text="npm i carbon-components-svelte"
  feedbackIcon={Checkmark}
  copy={copyInstallCommand}
  on:mouseenter={prefetchInstallCommand}
/>
CodeSnippet async copy

NotificationQueue: in-place update

queue.update(id, patch) merges into an existing toast instead of remove-and-re-add. Handy for loading → success flows while deduplication by id still applies.

Returns true when a notification with that id was found.

See the NotificationQueue update docs.

<script>
  import { NotificationQueue } from "carbon-components-svelte";

  let queue;

  async function save() {
    const id = "save-job";
    queue.add({ id, kind: "info", title: "Saving…", timeout: 0 });

    try {
      await persist();
      queue.update(id, { kind: "success", title: "Saved", timeout: 4000 });
    } catch (error) {
      queue.update(id, {
        kind: "error",
        title: "Save failed",
        subtitle: String(error),
        timeout: 0,
      });
    }
  }
</script>

<NotificationQueue bind:this={queue} />
notification-queue.mov

MultiSelect: bind sortedItems

bind:sortedItems gives you the list after sortItem and selectionFeedback run. You do not need to re-derive order from items, selectedIds, and sort settings in app code.

See the MultiSelect sorted items docs.

<script>
  import { MultiSelect } from "carbon-components-svelte";

  let sortedItems = [];
</script>

<MultiSelect
  bind:sortedItems
  selectionFeedback="top"
  titleText="Roles"
  items={[
    { id: "admin", text: "Admin" },
    { id: "editor", text: "Editor" },
    { id: "viewer", text: "Viewer" },
  ]}
/>

<p>Rendered order: {sortedItems.map((item) => item.text).join(", ")}</p>
MultiSelect sorted items

DatePicker: programmatic calendar access and readonly inputs

bind:calendar exposes the Flatpickr instance. Open the picker from an outside button or call set at runtime. DatePickerInput accepts readonly on single and range pickers.

See programmatic calendar access and read-only state.

<script>
  import { Button, DatePicker, DatePickerInput } from "carbon-components-svelte";

  let calendar;
</script>

<DatePicker bind:calendar>
  <DatePickerInput readonly labelText="Ship date" placeholder="mm/dd/yyyy" />
</DatePicker>

<Button kind="ghost" on:click={() => calendar?.open()}>Open calendar</Button>
calendar.mov

TimePicker: helper text, warning state, and readonly

helperText is new. warn / warnText match other form controls. readonly on TimePickerSelect keeps period and timezone selectors in sync with the field.

See warning state and read-only state.

<script>
  import { SelectItem, TimePicker, TimePickerSelect } from "carbon-components-svelte";
</script>

<TimePicker
  warn
  warnText="Time is outside business hours"
  helperText="24-hour format"
  labelText="Cron job"
  placeholder="hh:mm"
  value="22:30"
>
  <TimePickerSelect value="pm">
    <SelectItem value="am" text="AM" />
    <SelectItem value="pm" text="PM" />
  </TimePickerSelect>
</TimePicker>
TimePicker helper text TimePicker warning state

TreeView: keyboard navigation and multiselect

Keyboard behavior with link rows, collapsed parents, and multiselect:

  • Home / End jump to the first/last visible row
  • Ctrl+A selects all visible rows in multiselect mode
  • Enter expands or collapses a parent; Space selects
  • Arrow keys work on href link rows; subtrees get corrected aria-owns / aria-labelledby

See the TreeView docs.

<script>
  import { TreeView } from "carbon-components-svelte";

  let selectedIds = [];
  let nodes = [
    {
      id: "docs",
      text: "Docs",
      href: "/docs",
      nodes: [{ id: "guide", text: "Guide", href: "/docs/guide" }],
    },
  ];
</script>

<TreeView {nodes} multiselect bind:selectedIds />
TreeView multi-select

Breadcrumb: small size and accessibility fixes

size="sm" for a compact trail. labelText sets the nav aria-label (default "Breadcrumb"). isCurrentPage puts aria-current="page" on the link and the current-page style. Skeleton markup is hidden from assistive tech.

See the Breadcrumb small size docs.

<script>
  import { Breadcrumb, BreadcrumbItem } from "carbon-components-svelte";
</script>

<Breadcrumb size="sm" labelText="Project navigation">
  <BreadcrumbItem href="/">Home</BreadcrumbItem>
  <BreadcrumbItem href="/reports">Reports</BreadcrumbItem>
  <BreadcrumbItem href="/reports/2026" isCurrentPage>2026</BreadcrumbItem>
</Breadcrumb>
Breadcrumb size small

Modal and UI Shell: body scroll lock

Modal locks body scroll. If the mobile SideNav overlay is open at the same time, the lock is ref-counted and page scroll stays off until both close. Opening a modal no longer unlocks scroll while the drawer is still up.

See UIShell modal interaction.


List-box ARIA and menu keyboard shortcuts

ComboBox, Dropdown, and MultiSelect got ARIA fixes in one pass: aria-haspopup, aria-activedescendant indexed against filtered items, aria-describedby on helper/error/warn text, stable menu ids. ContextMenu, OverflowMenu, and ContentSwitcher pick up Home / End.

ListBoxMenu has portalHostClass so portalled menus can carry wrapper classes Carbon styles expect (MultiSelect uses this when portalMenu is on).


FileUploader: duplicate prevention and input ref

FileUploaderButton takes preventDuplicate to skip files that match an existing selection by name, size, and lastModified. FileUploader exposes ref on the inner file input.

See prevent duplicate files.

<script>
  import { FileUploaderButton } from "carbon-components-svelte";
</script>

<FileUploaderButton multiple preventDuplicate labelText="Add files" />

Docs: page size in the Copy Markdown tooltip

Component documentation supports Markdown format for easier use with LLMs.

Each component docs page has a Copy Markdown button. Hover it and the tooltip shows the rendered page size (e.g. 12 kB) and an estimated token count (e.g. ~3k tokens, using 1 kB ≈ 250 tokens). You see the cost before pasting into an agent.


Performance: list components and DataTable

  • ComboBox and MultiSelect stop rebuilding an index Map on every typeahead keystroke
  • Dropdown scrolls to the selected row in virtualized lists via itemIndexById and drops a redundant selected-index map
  • DataTable drops unused reactive work and imperative refSelectAll.checked syncing
  • DatePicker skips Flatpickr updates when nothing relevant changed
  • TreeView hides collapsed nodes with CSS instead of extra DOM churn
  • ToolbarSearch does not refilter when unrelated state updates

Breaking changes: decorative notification icon descriptions removed

These props were decorative. Icons are already hidden from assistive technology. They are no longer accepted:

  • InlineNotification: statusIconDescription
  • ToastNotification: statusIconDescription
  • NotificationQueue: statusIconDescription on queued notifications
  • NotificationIcon: iconDescription

Delete them from your code. No replacement.

DatePickerSkeleton swaps an unused <label> for a <span> in the loading skeleton (#2890).


What's Changed

Breaking Changes

  • fix(date-picker-skeleton)!: replace unused label with span by @metonym in #2890
  • fix(inline-notification)!: remove decorative statusIconDescription prop by @metonym in 6595f97
  • fix(notification-icon)!: remove decorative iconDescription prop by @metonym in 985e97d
  • fix(notification-queue)!: remove decorative statusIconDescription prop by @metonym in 6642c8e
  • fix(toast-notification)!: remove decorative statusIconDescription prop by @metonym in 4017539

Features

Bug Fixes

  • fix(breadcrumb-item): improve description for slot usage by @metonym in da27d82
  • fix(breadcrumb-item): type $$restProps by @metonym in 511dad9
  • fix(breadcrumb): apply current class when aria-current="page" is set by @metonym in #2953
  • fix(breadcrumb): avoid leaking aria-current onto <li> wrapper by @metonym in 2ba5659
  • fix(breadcrumb): hide skeleton from assistive tech by @metonym in #2951
  • fix(breadcrumb): set aria-current="page" on link when isCurrentPage is true by @metonym in #2958
  • fix(button): dedupe size class logic by @metonym in #2949
  • fix(button): omit href when disabled to avoid invalid <button href> by @metonym in #2948
  • fix(button): remove redundant aria-label from icon by @metonym in #2946
  • fix(button): set rel="noopener noreferrer" on target="_blank" anchors by @metonym in #2945
  • fix(checkbox-group): unsubscribe from selectedValue on destroy by @metonym in 743f668
  • fix(checkbox): associate helperText with input via aria-describedby by @metonym in #3078
  • fix(checkbox): prevent forwarded change event when readonly by @metonym in #3080
  • fix(clickable-tile): forward keyup/focus/blur events by @metonym in 1469fe8
  • fix(clickable-tile): guard clicked toggle when disabled by @metonym in #2957
  • fix(code-snippet): default inline copy button label to "Copy code" by @metonym in #2962
  • fix(code-snippet): mark textbox container as readonly by @kevinepena in #2964
  • fix(code-snippet): re-attach modal observer when portalTooltip toggles by @metonym in 52ff178
  • fix(code-snippet): remove redundant aria-label from expand chevron by @metonym in #2963
  • fix(combo-box): default aria-activedescendant to empty when none highlighted by @metonym in #2918
  • fix(combo-box): index aria-activedescendant against filtered items by @metonym in #2916
  • fix(combo-box): remove aria-labelledby shadowing label association by @metonym in #2917
  • fix(combo-box): set aria-haspopup="listbox" on input by @metonym in #2914
  • fix(combo-box): suppress invalid/warn states when disabled or read-only by @metonym in eabd15e
  • fix(combo-box): wire aria-describedby to helper/error/warn messages by @metonym in 988f688
  • fix(contained-list): remove redundant role="list" by @metonym in #3041
  • fix(content-switcher): forward focus/blur events by @metonym in a1c1547
  • fix(content-switcher): scope tab lookup to registered switches by @metonym in #2970
  • fix(content-switcher): support Home/End keyboard shortcuts by @metonym in #2968
  • fix(content-switcher): sync switch order to DOM on conditional render by @metonym in b549fbf
  • fix(content-switcher): unregister Switch from parent on destroy by @metonym in #2969
  • fix(context-menu-option): apply aria-disabled to submenu triggers by @metonym in 9bb37d0
  • fix(context-menu-option): disabled leaf option should not close the menu by @metonym in 95f3ad0
  • fix(context-menu-option): only apply class if disabled by @metonym in #2972
  • fix(context-menu): dispatch open/close only on open-state transitions by @metonym in #2975
  • fix(context-menu): rebind listeners when target changes by @metonym in #2977
  • fix(context-menu): reset role to "menuitem" when option no longer selectable by @metonym in #2976
  • fix(copy-button): avoid dispatching "copy" event if already clicked by @metonym in #3141
  • fix(copy-button): re-attach modal observer when portalTooltip toggles by @metonym in bd9c862
  • fix(css): suppress native copy feedback caret for portalled tooltips by @metonym in #2961
  • fix(data-table): click:header reports intended direction even when sort is cancelled by @metonym in #2940
  • fix(data-table): detect in-place row mutations in cell cache by @metonym in #2941
  • fix(data-table): preserve active filter when rows prop changes by @metonym in #2944
  • fix(data-table): scope TableHeader ids to the DataTable instance by @metonym in #2939
  • fix(date-picker-input): forward focus event to input by @metonym in #2889
  • fix(date-picker-skeleton): replace unused label with span by @metonym in #2890
  • fix(date-picker): avoid animation flicker if calendar is already open by @metonym in #2888
  • fix(date-picker): constrain input width if helperText is longer than input by @b-r-i-a-n-w-e-s-t in 829a6c5
  • fix(date-picker): disabled state icon uses correct color by @metonym in 135d046
  • fix(date-picker): portalled menu should track scroll/resize events by @metonym in 71c713c
  • fix(date-picker): restore single-letter weekday abbreviations for locale="en" by @metonym in c450d81
  • fix(date-picker): style disabled month nav buttons by @metonym in 39b3fd3
  • fix(date-picker): suppress invalid/warn states when disabled or read-only by @metonym in 4d9fa04
  • fix(dropdown): exclude "Space" from typeahead buffer by @metonym in #2928
  • fix(dropdown): gate aria-controls on open by @metonym in #2927
  • fix(dropdown): scroll selected option into view when menu overflows by @metonym in 6bc4d64
  • fix(dropdown): suppress invalid/warn states when disabled or read-only by @metonym in 3a9eb2e
  • fix(dropdown): wire aria-describedby to helper/error/warn messages by @metonym in #2932
  • fix(file-uploader): stable per-file ids for add/remove events by @metonym in #3017
  • fix(inline-loading): clear success timer before rescheduling by @metonym in 00f283d
  • fix(inline-notification): remove decorative statusIconDescription prop by @metonym in 6595f97
  • fix(inline-notification): type role as "alert" | "log" | "status" by @metonym in 39c4308
  • fix(link): forward focus/blur/keydown/keyup events by @metonym in e3526be
  • fix(list-box): add safer guard for rendering invalidText/warnText by @metonym in 57c828b
  • fix(local-storage): drop stray argument from internal setItem calls by @metonym in d59de93
  • fix(local-storage): sync prevValue after reactive key change by @metonym in fa68b4d
  • fix(modal): implement body scroll lock by @metonym in da2e81c
  • fix(modal): skip Enter submit when target is textarea/select/contenteditable by @metonym in #2994
  • fix(multi-select): apply wrapper classes to portal host if portalMenu is true by @metonym in 2392bad
  • fix(multi-select): close menu on focus leaving wrapper by @metonym in #2920
  • fix(multi-select): do not open menu on filterable input focus by @metonym in #2921
  • fix(multi-select): preserve bound value on non-filterable close by @metonym in #2924
  • fix(multi-select): recompute sortedItems when items prop changes by @metonym in #2922
  • fix(multi-select): remove aria-labelledby shadowing label association by @metonym in #2923
  • fix(multi-select): set aria-haspopup="listbox" on input by @metonym in #2919
  • fix(multi-select): suppress invalid/warn states when disabled or read-only by @metonym in 12e464a
  • fix(notification-icon): remove decorative iconDescription prop by @metonym in 985e97d
  • fix(notification-queue): remove decorative statusIconDescription prop by @metonym in 6642c8e
  • fix(number-input): set aria-readonly for readonly state by @kevinepena in #3037
  • fix(outbound-link): forward focus/blur/keydown/keyup events by @metonym in 1fff120
  • fix(overflow-menu-item): primaryFocus should not be reactive by @metonym in #2987
  • fix(overflow-menu-item): set aria-disabled on disabled link by @metonym in #2983
  • fix(overflow-menu): aria-controls is stable by @metonym in #2986
  • fix(overflow-menu): prevent infinite loop when all items are disabled by @metonym in #2984
  • fix(overflow-menu): self-unregister items on destroy by @metonym in #3011
  • fix(overflow-menu): use aria-haspopup="menu" by @metonym in #2985
  • fix(overflow-menu): use SvelteHTMLElements to type target/rel props by @metonym in #3003
  • fix(package): include CSS type definitions in package.json exports map by @metonym in e806405
  • fix(pagination-nav): announce current page as 1-based in live region by @metonym in #3070
  • fix(pagination-nav): label active middle page with "Active, Page" by @metonym in #3071
  • fix(pagination): use clear suffixes for internal IDs by @metonym in #2990
  • fix(progress-bar): associate label via aria-labelledby by @metonym in #3074
  • fix(progress-indicator): remove unmounted steps from context by @metonym in #3049
  • fix(progress-step): dispatch change once per keypress by @metonym in #3054
  • fix(progress-step): do not throw if rendered outside context by @metonym in #3081
  • fix(progress-step): forward focus/blur events by @metonym in #3055
  • fix(radio-button-group): associate helperText via aria-describedby by @metonym in #3085
  • fix(radio-button-group): unsubscribe from selectedValue on destroy by @metonym in #3061
  • fix(select): add missing warn icon/text for inline variant by @metonym in #2913
  • fix(select): suppress invalid/warn states when disabled or read-only by @metonym in 62df7cd
  • fix(selectable-tile): announce label as checkbox to screen readers by @metonym in #3075
  • fix(selectable-tile): hide checkmark icon from assistive technology by @metonym in #3107
  • fix(selectable-tile): re-register with group when value changes by @metonym in #3067
  • fix(selectable-tile): set aria-disabled for disabled state by @metonym in #3065
  • fix(session-storage): drop stray argument from internal setItem calls by @metonym in bf72abf
  • fix(session-storage): sync prevValue after hydration and reactive key change by @metonym in d5d81c3
  • fix(slider): move keyboard handler to thumb with role="slider" by @metonym in #3092
  • fix(slider): suppress invalid/warn states when disabled or read-only by @metonym in e8420df
  • fix(slider): update value on click without drag by @metonym in #3046
  • fix(structured-list-input): do not throw if rendered outside context by @metonym in #3033
  • fix(structured-list-row): make selectable rows keyboard-activatable by @metonym in #3091
  • fix(structured-list): prevent dispatching initial "change" event by @metonym in #3032
  • fix(switch): forward focus/blur/keyup events by @metonym in bc7332e
  • fix(switch): remove useless preventDefault modifier by @metonym in #2967
  • fix(tabs): prevent infinite loop when all items are disabled by @metonym in #3129
  • fix(tag): SelectableTag uses aria-pressed instead of aria-checked by @metonym in #3073
  • fix(time-picker-select): forward change/input/focus/blur events by @metonym in #2899
  • fix(time-picker): associate invalidText with input via aria-describedby by @metonym in #2894
  • fix(time-picker): invalid state has icon by @metonym in #2896
  • fix(toast-notification): remove decorative statusIconDescription prop by @metonym in 4017539
  • fix(toast-notification): type role as "alert" | "log" | "status" by @metonym in 3f65834
  • fix(toggle): do not render empty label span when no label provided by @metonym in #3137
  • fix(toolbar-batch-actions): stop mutating controlled active prop by @metonym in #3022
  • fix(toolbar-content): unsubscribe from batchActionsActive on destroy by @metonym in 6b71325
  • fix(toolbar-menu): reset parent overflow on unmount by @metonym in #3029
  • fix(toolbar-search): coerce value to string before reading length by @metonym in #3023
  • fix(toolbar-search): skip expanded toggle when persistent by @metonym in #3025
  • fix(tooltip-footer): do not throw if rendered outside context by @metonym in #3047
  • fix(tree-view-item): ArrowLeft on collapsed parent focuses its ancestor by @metonym in ade0f55
  • fix(tree-view-item): ArrowRight reliably focuses the first child row by @metonym in 924150f
  • fix(tree-view-item): Enter toggles expand on parent; Space only selects by @metonym in ff54c2a
  • fix(tree-view-item): export findParentTreeNode by @metonym in e7a2644
  • fix(tree-view-item): wire aria-owns and aria-labelledby for subtree group by @metonym in faa3bab
  • fix(tree-view): arrow keys work with link rows by @metonym in #3101
  • fix(tree-view): re-apply first focusable tabindex when nodes change by @metonym in 841355f
  • fix(tree-view): roving tabindex resets the previous row on focus move by @metonym in f69710d
  • fix(tree-view): support Ctrl+A to select all in multiselect by @metonym in a07027f
  • fix(tree-view): support Home and End keys for focus navigation by @metonym in 8f1c11d
  • fix(tree-view): tree walker handles non-Element nodes and skips hidden rows by @metonym in 0b0e5e3
  • fix(ui-shell): Header preserves user-toggled side nav across breakpoints by @metonym in #3013
  • fix(ui-shell): HeaderAction allows content overflow and uses correct color scheme by @metonym in 30b0b9e
  • fix(ui-shell): HeaderActionLink uses correct focus outline color by @metonym in #3154
  • fix(ui-shell): HeaderNav does not forward aria-label to menu bar by @metonym in #3028
  • fix(ui-shell): HeaderShell uses unique IDs by @metonym in #3010
  • fix(ui-shell): SideNav locks body scroll when used with modal by @metonym in ddc76aa
  • fix(ui-shell): guard ref access in window click handlers by @metonym in #3012
  • fix: avoid literal false tokens in composed class attributes by @metonym in #2960
  • fix: drop literal "undefined" token from interpolated class strings by @metonym in #2992

Performance

  • perf(combo-box): drop per-keystroke index Map for findIndex by @metonym in #3088
  • perf(combo-box): remove dead code for single-filtered-item by @metonym in #2934
  • perf(combo-box): remove no-op highlightedId writes by @metonym in #2915
  • perf(data-table): remove imperative refSelectAll.checked by @metonym in #2937
  • perf(data-table): remove unused expandedRowsHeight reactive by @metonym in #2942
  • perf(date-picker): skip redundant flatpickr updates on reactive ticks by @metonym in #2892
  • perf(dropdown): drop selected index map by @metonym in #3090
  • perf(dropdown): use itemIndexById for virtualized scroll-to-selected by @metonym in #2935
  • perf(multi-select): drop per-keystroke index Map for findIndex by @metonym in #3089
  • perf(toolbar-search): avoid refiltering on unrelated updates by @metonym in #3024
  • perf(tree-view-node): hide collapsed nodes via CSS by @metonym in #3007

Don't miss a new carbon-components-svelte release

NewReleases is sending notifications on new releases.