Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
86918f4
🐊
jpzwarte Apr 14, 2026
2b17554
⚽️
jpzwarte Apr 14, 2026
fe0a787
Merge branch 'main' into fix/2868-tag-list-a11y
jpzwarte Apr 21, 2026
490440b
🏃
jpzwarte Apr 21, 2026
bfbcdf0
Update packages/components/tag/src/tag.spec.ts
jpzwarte Apr 21, 2026
b6ae79f
🌇
jpzwarte Apr 21, 2026
c51a9cb
Merge remote-tracking branch 'origin/main' into fix/2868-tag-list-a11y
Diaan Jun 11, 2026
98b5a99
fix(tag-list): improve a11y
michal-sanoma Jun 11, 2026
0302a07
fix(tag-list): improve a11y
michal-sanoma Jun 12, 2026
cf0a41d
fix(tag-list): improve a11y
michal-sanoma Jun 15, 2026
4e8aa7b
Merge branch 'main' into fix/2868-tag-list-a11y
michal-sanoma Jun 15, 2026
48218ed
Apply suggestions from code review
michal-sanoma Jun 15, 2026
76bb8d7
fix(tag-list): improve a11y
michal-sanoma Jun 15, 2026
7672e8a
fix(tag-list): improve a11y
michal-sanoma Jun 15, 2026
1c3c367
Apply suggestions from code review
michal-sanoma Jun 15, 2026
f088cb5
fix(tag-list): improve a11y
michal-sanoma Jun 15, 2026
d33a513
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
3daeb3e
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
aeb2438
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
2fc9d0e
Apply suggestions from code review
michal-sanoma Jun 16, 2026
cc02e18
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
e99294d
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
d7acabc
Merge branch 'main' into fix/2868-tag-list-a11y
michal-sanoma Jun 16, 2026
ffdd204
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
5bd5451
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
9a32255
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
e0cba45
Potential fix for pull request finding
michal-sanoma Jun 16, 2026
3b5c151
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
9036dc2
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
6200ddb
fix(tag-list): improve a11y
michal-sanoma Jun 16, 2026
f913b4f
fix(tag-list): improve a11y
michal-sanoma Jun 17, 2026
0a913f5
fix(tag-list): improve a11y
michal-sanoma Jun 17, 2026
f19371c
fix(tag-list): improve a11y
michal-sanoma Jun 17, 2026
e2e2a03
fix(tag-list): improve a11y
michal-sanoma Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/chatty-badgers-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@sl-design-system/tag': patch
---

Accessibility improvements to `<sl-tag>` and `<sl-tag-list>`:

- The remove button now has a proper accessible label ("Remove tag 'X'") instead of being `aria-hidden`
- The remove button uses `aria-disabled` instead of `disabled`, keeping it keyboard-reachable when the tag is disabled
- Focus is delegated to the remove button via the component's `focus()` implementation; `:state(focus-visible)` tracks focus for styling
- `<sl-tag-list>` correctly sets `role="listitem"` on each tag
5 changes: 5 additions & 0 deletions .changeset/shiny-shrimps-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sl-design-system/combobox': patch
---

Improved keyboard focus behavior for removable selected tags in multi-select comboboxes. Focus now stays within the selected tag list when removing tags one by one and only returns to the input after the last tag is removed. The combobox also avoids showing a separate fake tag focus indicator when focus is on a tag remove button
5 changes: 5 additions & 0 deletions .changeset/stupid-cougars-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sl-design-system/locales': patch
---

Replace `sl.tag.removalInstructions` with `sl.tag.remove` (a parameterized string for the remove button label, e.g. "Remove tag 'X'") and add `sl.tagList.navigationInstructions` for tag-list roving tabindex instructions.
4 changes: 3 additions & 1 deletion .storybook/stories/links.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export default {
In the theme is a global.css file containing styling for links. This works as a global style for
links in your application and links in the light dom of SLDS components.
<br />
<a href="https://sanomalearning.design" target="_blank" rel="noopener noreferrer">Sanoma Learning Design system</a>
<a href="https://sanomalearning.design" target="_blank" rel="noopener noreferrer"
>Sanoma Learning Design system</a
>
`
} satisfies Meta<Props>;

Expand Down
5 changes: 0 additions & 5 deletions packages/components/button/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
### Patch Changes

- [#3299](https://github.com/sl-design-system/components/pull/3299) [`78e7333`](https://github.com/sl-design-system/components/commit/78e733338fd67ef59797b3e02b22907fe0f5c638) - Minor style fixes:

- fix icon-only size regression by using `box-sizing: content-box`
- fix `<button>` growing larger than the host `<sl-button>`

- [#3360](https://github.com/sl-design-system/components/pull/3360) [`7163d4e`](https://github.com/sl-design-system/components/commit/7163d4ee4cb47e4db591aceba2e3978f8f31b2c7) - Styling fixes:

- Fix WebKit bug where the aspect-ratio of the inner button is ignored
- Fix bug where the inner button doesn't grow with the host element

Expand All @@ -34,7 +32,6 @@
### Major Changes

- [#3139](https://github.com/sl-design-system/components/pull/3139) [`50590de`](https://github.com/sl-design-system/components/commit/50590de476ff108cc28b865dbc96e3ca48399538) - Breaking changes:

- The component now renders a native `<button>` inside the shadow DOM, changing the DOM structure
- The `icon-only` attribute and `iconOnly` property have been removed in favor of the `:state(icon-only)` CSS custom state

Expand Down Expand Up @@ -89,7 +86,6 @@
### Minor Changes

- [#2646](https://github.com/sl-design-system/components/pull/2646) [`f025c0f`](https://github.com/sl-design-system/components/commit/f025c0f3cbb83b72c80563e9d989402608add193) - Various improvements:

- Fix missing inverted + disabled styling
- Add support for `aria-disabled="true"`

Expand Down Expand Up @@ -146,7 +142,6 @@
### Minor Changes

- [#1675](https://github.com/sl-design-system/components/pull/1675) [`389d0e2`](https://github.com/sl-design-system/components/commit/389d0e2a982dd40b4e3a04cf3b1d8b34204236a0) - Button improvements:

- Added a new `shape` property that defaults to `square` but also accepts `pill` for rounded corners
- Added a new `inverted` variant, to be used on dark/light background (depending on light/dark mode)
- Removed default values of `fill`, `size`, `type` and `variant` properties
Expand Down
5 changes: 0 additions & 5 deletions packages/components/combobox/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
### Patch Changes

- [#3211](https://github.com/sl-design-system/components/pull/3211) [`20a1178`](https://github.com/sl-design-system/components/commit/20a1178f0f1548bd083df7d337ecba443daf579f) - Functional changes:

- The popover opens when you click in the combobox, no longer when you enter the combobox with keyboard navigation.

Accessibility improvements:

- Forward ARIA attributes (`aria-label`, `aria-describedby`, `aria-labelledby`) from host element to the input element for proper screen reader support
- Automatically associate label with input via `aria-labelledby` when a label is present

Expand Down Expand Up @@ -175,7 +173,6 @@
```

You can customize the rendering of each option by using:

- `optionLabelPath` to specify the path to the label in each option object
- `optionValuePath` to specify the path to the value in each option object

Expand All @@ -185,7 +182,6 @@
the options in both scenarios by using the `sl-option { ... }` selector.

- [#1642](https://github.com/sl-design-system/components/pull/1642) [`cef2371`](https://github.com/sl-design-system/components/commit/cef2371d5868439edbba8156bf38c167b72f0f39) - Various combobox fixes:

- Add `aria-owns` for linking the input to the listbox
- Add `aria-posinset` and `aria-setsize` to the listbox options for virtual lists
- Add focus style to tags
Expand All @@ -208,7 +204,6 @@
### Patch Changes

- [#1599](https://github.com/sl-design-system/components/pull/1599) [`4714b36`](https://github.com/sl-design-system/components/commit/4714b36f1387d4d1731a310b621caf5a33be105b) - Various a11y related fixes/improvements:

- The label was associated with the `<sl-combobox>` element instead of the `<input>` element
- `aria-selected="false"` was missing on the non-selected options
- `aria-multiselectable="true"` was missing on the listbox when the multiple property is set
Expand Down
51 changes: 49 additions & 2 deletions packages/components/combobox/src/combobox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1113,14 +1113,14 @@ describe('sl-combobox', () => {
expect(tags.every(tag => tag.hasAttribute('aria-hidden'))).to.be.false;
});

it('should set aria-hidden on sl-tag elements when not disabled', async () => {
it('should not set aria-hidden on sl-tag elements when not disabled', async () => {
el.disabled = false;
await el.updateComplete;

const tags = Array.from(el.renderRoot.querySelectorAll('sl-tag'));

expect(tags).to.have.lengthOf(2);
expect(tags.every(tag => tag.getAttribute('aria-hidden') === 'true')).to.be.true;
expect(tags.every(tag => tag.hasAttribute('aria-hidden'))).to.be.false;
});
});

Expand Down Expand Up @@ -1259,6 +1259,18 @@ describe('sl-combobox', () => {
expect(removable).to.be.true;
});

it('should not show fake tag focus when navigating remove buttons', async () => {
const tags = Array.from(el.renderRoot.querySelectorAll('sl-tag')),
button = tags[0].renderRoot.querySelector('button');

button?.dispatchEvent(
new KeyboardEvent('keydown', { bubbles: true, composed: true, key: 'ArrowRight' })
);
await el.updateComplete;

expect(tags.some(tag => tag.classList.contains('focused'))).to.be.false;
});

it('should stack options when there is limited space', async () => {
vi.useFakeTimers();

Expand Down Expand Up @@ -1355,6 +1367,41 @@ describe('sl-combobox', () => {
// Verify the tag was removed
expect(el.value).to.deep.equal([]);
});

it('should focus the next tag after removing a tag', async () => {
el.value = ['Option 1', 'Option 2', 'Option 3'];
await el.updateComplete;

const tags = Array.from(el.renderRoot.querySelectorAll('sl-tag'));

tags[0].renderRoot.querySelector<HTMLElement>('button')?.focus();
await userEvent.keyboard('{Enter}');
await el.updateComplete;
await waitForNextFrame();

const remainingTags = Array.from(el.renderRoot.querySelectorAll('sl-tag'));

expect(el.value).to.deep.equal(['Option 2', 'Option 3']);
expect((el.renderRoot as ShadowRoot).activeElement).to.equal(remainingTags[0]);
expect(remainingTags[0].shadowRoot?.activeElement).to.equal(
remainingTags[0].renderRoot.querySelector('button')
);
});

it('should focus the input after removing the last tag', async () => {
el.value = ['Option 1'];
await el.updateComplete;

const tag = el.renderRoot.querySelector('sl-tag')!;

tag.renderRoot.querySelector<HTMLElement>('button')?.focus();
await userEvent.keyboard('{Enter}');
await el.updateComplete;
await waitForNextFrame();

expect(el.value).to.deep.equal([]);
expect(document.activeElement).to.equal(input);
});
});

describe('allow custom values', () => {
Expand Down
49 changes: 45 additions & 4 deletions packages/components/combobox/src/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
type SlChangeEvent,
type SlFocusEvent
} from '@sl-design-system/shared/events.js';
import { Tag, TagList } from '@sl-design-system/tag';
import { type SlRemoveEvent, Tag, TagList } from '@sl-design-system/tag';
import { TextField } from '@sl-design-system/text-field';
import {
type CSSResultGroup,
Expand Down Expand Up @@ -504,6 +504,7 @@ export class Combobox<T = any, U = T> extends ObserveAttributesMixin(
${this.multiple && this.selectedItems.length
? html`
<sl-tag-list
@focusin=${this.#onTagListFocusIn}
?disabled=${this.disabled}
aria-label=${msg('Selected options', { id: 'sl.combobox.selectedOptions' })}
size=${ifDefined(this.size)}
Expand All @@ -514,10 +515,9 @@ export class Combobox<T = any, U = T> extends ObserveAttributesMixin(
item => item,
item => html`
<sl-tag
@sl-remove=${() => this.#onRemove(item)}
@sl-remove=${(event: SlRemoveEvent) => this.#onRemove(item, event)}
?disabled=${this.disabled}
?removable=${!this.disabled}
aria-hidden=${this.disabled ? nothing : 'true'}
class=${this.focusedTag === item ? 'focused' : ''}>
${item.label}
</sl-tag>
Expand Down Expand Up @@ -683,6 +683,10 @@ export class Combobox<T = any, U = T> extends ObserveAttributesMixin(
}

#onKeydown(event: KeyboardEvent): void {
if (!event.composedPath().includes(this.input)) {
return;
}

const isSelectOnlySpace = !!this.selectOnly && event.key === ' ';

if ((event.key === 'Enter' || isSelectOnlySpace) && !this.focusedTag) {
Expand Down Expand Up @@ -807,14 +811,51 @@ export class Combobox<T = any, U = T> extends ObserveAttributesMixin(
this.#pointerDown = false;
}

#onRemove(item: ComboboxItem<T, U>): void {
#onRemove(item: ComboboxItem<T, U>, event?: SlRemoveEvent): void {
const nextFocusedItem = event ? this.#getNextSelectedTagItem(item) : undefined;

this.#removeSelectedOption(item);
this.#updateFilteredOptions();
this.#updateCurrent();

if (this.#popoverJustClosed) {
this.wrapper?.showPopover();
}

if (event) {
void this.updateComplete.then(() => {
requestAnimationFrame(() => this.#focusSelectedTag(nextFocusedItem));
});
}
}

#onTagListFocusIn(): void {
this.focusedTag = undefined;
}

#getNextSelectedTagItem(item: ComboboxItem<T, U>): ComboboxItem<T, U> | undefined {
const tags = Array.from(this.renderRoot.querySelectorAll<Tag>('sl-tag')),
visibleTagItems = tags
.map((tag, index) => ({ item: this.selectedItems[index], tag }))
.filter(({ item, tag }) => item && tag.removable && tag.style.display !== 'none'),
index = visibleTagItems.findIndex(({ item: tagItem }) => tagItem === item);

return visibleTagItems[index + 1]?.item ?? visibleTagItems[index - 1]?.item;
}

#focusSelectedTag(item?: ComboboxItem<T, U>): void {
const tags = Array.from(this.renderRoot.querySelectorAll<Tag>('sl-tag')),
tag = item ? tags[this.selectedItems.indexOf(item)] : undefined,
focusTarget =
tag && tag.style.display !== 'none'
? tag
: tags.find(tag => tag.removable && tag.style.display !== 'none');

if (focusTarget) {
focusTarget.focus();
} else {
this.input.focus();
}
}

/** Updates the list of options and the listbox link with the text input. */
Expand Down
1 change: 0 additions & 1 deletion packages/components/form/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
### Minor Changes

- [#3248](https://github.com/sl-design-system/components/pull/3248) [`fc60898`](https://github.com/sl-design-system/components/commit/fc60898ea3c7b5b234a13c6bf157e89528f3a11f) - Standardized warning and error icons:

- Changed `warning` icons from `octagon-exclamation-solid` to `triangle-exclamation-solid` in Callout, Inline message, and Progress bar.
- Changed `circle-exclamation-solid` to `triangle-exclamation-solid` in validation messages in the Form field.
- Changed `error/danger` icons from `diamond-exclamation-solid` or `octagon-exclamation-solid` to the new `octagon-xmark-solid` icon in Callout, Inline message, and Progress bar. Make sure to update your theme if you update any of these components.
Expand Down
6 changes: 0 additions & 6 deletions packages/components/grid/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@
### Minor Changes

- [#2034](https://github.com/sl-design-system/components/pull/2034) [`1072075`](https://github.com/sl-design-system/components/commit/1072075e3f1b5f0bf8b07dc1f89fd39b9f7103d0) - Big improvements:

- New visual styles throughout all components
- Refactored to use the new `ListDataSource` class and view model types
- Removed `SelectionController` since that logic is now part of `ListDataSource`
Expand All @@ -224,7 +223,6 @@
This change removes the `activatable-row` property and instead leaves it to the user to set the `activeRow` property on the `sl-grid` component. The examples in Storybook now also use a button with an avatar to activate the row, which is more accessible than using a checkbox. This fixes the issue we had before where we could not find a solution how to make the row activatable with the keyboard, while also keeping the checkbox for selection. Now, the row can be activated with the keyboard by focusing the button and pressing Enter or Space. And at the same time, the checkbox can still be used for selection.

- [#2024](https://github.com/sl-design-system/components/pull/2024) [`a343e29`](https://github.com/sl-design-system/components/commit/a343e298d6b65966e04b3fbfc3598305a29bf1cc) - Grid improvements:

- Add "Cancel selection" button to the bulk action toolbar
- Add `column` argument to `GridColumnHeaderRenderer` type
- Fix missing aria-label on a selection column checkbox
Expand All @@ -248,7 +246,6 @@
### Patch Changes

- [#2077](https://github.com/sl-design-system/components/pull/2077) [`778e8a1`](https://github.com/sl-design-system/components/commit/778e8a1ae5dc7908e5c000a620b8143883c75a91) - - Adds option to have no skip table links

- Fixes issue for Safari (an other browsers that don't support native anchor positioning) where to "Skip to start of table" link was positioned incorrectly

- [#2072](https://github.com/sl-design-system/components/pull/2072) [`77b348d`](https://github.com/sl-design-system/components/commit/77b348d19a4869f9242d8ea1c70d32d1e6d04212) - Fix regression with basic drag and drop of rows within grid
Expand Down Expand Up @@ -349,7 +346,6 @@
- [#1791](https://github.com/sl-design-system/components/pull/1791) [`133b883`](https://github.com/sl-design-system/components/commit/133b883234d911dabe37bd3c8acef26afea20fe9) - Replace `--sl-size-borderWidth-subtle` with `--sl-size-borderWidth-default`

- [#1653](https://github.com/sl-design-system/components/pull/1653) [`f15d75c`](https://github.com/sl-design-system/components/commit/f15d75c6c3765b797f0bed57c5d1f2855cab4f7e) - Improve horizontal scrolling experience:

- Add shadows to the left and right of the grid when it is scrollable
- Add the new `<sl-scrollbar>` when the grid is horizontally scrollable
- Make sure the scrollbar is always visible (sticky at the bottom of the grid)
Expand All @@ -375,7 +371,6 @@
- [#1609](https://github.com/sl-design-system/components/pull/1609) [`515e2fb`](https://github.com/sl-design-system/components/commit/515e2fbbda7ecee92392b8ddf9f98c335fe32cf6) - Added tokens for grid

- [#1616](https://github.com/sl-design-system/components/pull/1616) [`b1e3b74`](https://github.com/sl-design-system/components/commit/b1e3b741e78400e3755ddaa0c5c4fdeed2e3f960) - Improved accessibilty of the table;

- Added aria-rowindex and aria-rowcount;
- Improved keyboardnavigation, including skip table links
- Changed the way selecting works; active row by clicking on the entire row and selecting a row by checking the checkbox
Expand All @@ -392,7 +387,6 @@

- [#1693](https://github.com/sl-design-system/components/pull/1693) [`4e57f9c`](https://github.com/sl-design-system/components/commit/4e57f9c60835a07db45f74fde73a3bf13b6abe51) - Refactor existing data sources into list specific datasources, clearing
the way to add `TreeDataSource` in the `@sl-design-system/tree` package.

- The base `DataSource` class has support for sorting and filtering
- Grouping and pagination has been moved to the `ListDataSource` class
- `ArrayDataSource` and `FetchDataSource` have been renamed to `ArrayListDataSource` and `FetchListDataSource` respectively
Expand Down
1 change: 0 additions & 1 deletion packages/components/listbox/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
### Patch Changes

- [#1632](https://github.com/sl-design-system/components/pull/1632) [`e68df34`](https://github.com/sl-design-system/components/commit/e68df344917a8d0bdc6a4c92f59079a247c6e7a9) - Add ability to render grouped items using lit-virtualizer:

- New `optionGroupPath` property to specify the path to the group name in the option object
- New `<sl-option-group-header>` component to render the group header
- Add `items` property for advanced customization of how options are rendered (used in combobox)
Expand Down
2 changes: 0 additions & 2 deletions packages/components/menu/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
- [#3001](https://github.com/sl-design-system/components/pull/3001) [`a7ac909`](https://github.com/sl-design-system/components/commit/a7ac90987881881bd0cb916c583e68c785b52622) - Improves accessibility of menu-groups with headers

- [#3022](https://github.com/sl-design-system/components/pull/3022) [`a4a0c23`](https://github.com/sl-design-system/components/commit/a4a0c23a5341a2026c23e6e7fdf05cfdd44dc16c) - - `MenuButton` and `Button` now correctly block click/keyboard activation when `aria-disabled` is set, but remain focusable for improved accessibility and tooltip support.

- Standalone `MenuButton` continues to use native `disabled` to remain non-focusable, while support for `aria-disabled` focusability has been improved.

- [#3034](https://github.com/sl-design-system/components/pull/3034) [`fd4a0d7`](https://github.com/sl-design-system/components/commit/fd4a0d79b4c0d9a1438b437bc7a1122f03d08c11) - Changed styling so the hover and active state have an indicator. This makes it more accessible because we don't rely on only a subtle change in the background color.
Expand Down Expand Up @@ -154,7 +153,6 @@
### Patch Changes

- [#1777](https://github.com/sl-design-system/components/pull/1777) [`67f5b81`](https://github.com/sl-design-system/components/commit/67f5b810558d124289f26e3cc3fb2c59da97bb5f) - Fixed several accessibility issues;

- Improved VoiceOver in support Chrome
- Fixed keyboard navigation in submenu
- Applied new tokens to menu and menu item
Expand Down
Loading
Loading