Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
cea790b
🚟
jpzwarte May 21, 2026
a0b4a8c
🏄
jpzwarte May 21, 2026
b087599
💚
jpzwarte May 21, 2026
e6172ae
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte May 21, 2026
7979d82
🏄
jpzwarte May 21, 2026
58f0fd5
🌐
jpzwarte May 21, 2026
1e00db6
🎭
jpzwarte May 21, 2026
b04be06
🎾
jpzwarte May 21, 2026
61d6159
👽
jpzwarte May 21, 2026
cbcb785
🐿
jpzwarte May 26, 2026
6a863c1
🌅
jpzwarte May 26, 2026
c023493
📺
jpzwarte May 26, 2026
1c5a951
🍹
jpzwarte May 26, 2026
d952039
🎈
jpzwarte May 26, 2026
2ec2c5e
🍹
jpzwarte May 26, 2026
32ef3e7
jpzwarte May 26, 2026
619f69e
🐛
jpzwarte May 26, 2026
caf4dc9
🔋
jpzwarte May 26, 2026
038e53e
🦄
jpzwarte May 26, 2026
4c27908
🐝
jpzwarte May 26, 2026
1bc5248
🛠
jpzwarte May 26, 2026
448df62
🍵
jpzwarte May 27, 2026
d8c1e1c
🐋
jpzwarte May 27, 2026
a260509
❄️
jpzwarte May 27, 2026
2a3072c
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte May 27, 2026
5236177
🔋
jpzwarte May 27, 2026
0cbe05c
😸
jpzwarte May 27, 2026
e6eb7d3
🌄
jpzwarte May 27, 2026
cf25977
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte May 27, 2026
36c87ae
🍉
jpzwarte May 28, 2026
5d7c664
🚒
jpzwarte Jun 1, 2026
3b05565
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte Jun 1, 2026
2cc6659
😋
jpzwarte Jun 1, 2026
f23ed32
🍒
jpzwarte Jun 1, 2026
dbf9e6e
🚃
jpzwarte Jun 1, 2026
ed764d3
🛩
jpzwarte Jun 1, 2026
933aaa2
🌭
jpzwarte Jun 3, 2026
ee21384
🎂
jpzwarte Jun 3, 2026
51c1e96
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte Jun 3, 2026
0a0c0a6
⭐️
jpzwarte Jun 3, 2026
15244c2
🦄
jpzwarte Jun 3, 2026
76bfd44
🐧
jpzwarte Jun 3, 2026
63a0b59
🏂
jpzwarte Jun 3, 2026
b8009a8
🐘
jpzwarte Jun 3, 2026
a944d65
🎖
jpzwarte Jun 3, 2026
52100b1
🐏
jpzwarte Jun 3, 2026
ddd256a
🏖
jpzwarte Jun 3, 2026
876ce64
🎨
jpzwarte Jun 3, 2026
7db9e85
🎊
jpzwarte Jun 3, 2026
56de807
🐽
jpzwarte Jun 3, 2026
8234ac1
🚣
jpzwarte Jun 3, 2026
6991922
🐼
jpzwarte Jun 3, 2026
5b26224
🎆
jpzwarte Jun 3, 2026
2f09a01
🐆
jpzwarte Jun 3, 2026
79f38ba
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte Jun 9, 2026
2d1fd1d
🐭
jpzwarte Jun 10, 2026
586a8eb
Merge branch 'main' into feature/3344-tooltip-property-on-button
jpzwarte Jun 16, 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
17 changes: 17 additions & 0 deletions .changeset/afraid-parents-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@sl-design-system/button': minor
'@sl-design-system/menu': minor
'@sl-design-system/tag': minor
'@sl-design-system/toggle-button': minor
---

Add `tooltip` property

Previously, adding a tooltip to any kind of component required adding a sibling `<sl-tooltip>` element manually and wiring up the correct `aria-describedby` or `aria-labelledby` relationship by hand. This was especially cumbersome for icon-only buttons, where the tooltip doubles as the accessible label.

The new `tooltip` property improves the Developer Experience by letting you set a tooltip directly on the component.

For buttons, it handles all the accessibility wiring automatically:

- For **icon-only buttons** the tooltip text acts as the accessible label (`aria-labelledby`).
- For **text buttons** the tooltip text acts as an accessible description (`aria-describedby`).
19 changes: 19 additions & 0 deletions .changeset/clean-chicken-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@sl-design-system/toggle-button': minor
---

Refactor toggle button to use an internal `<button>` element. This improves accessibility and removes the need for manual keyboard and ARIA handling.

**Breaking changes**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with this many breaking changes, is it still a minor version update?


- The `[pressed]`, `[icon-only]`, `[text-only]`, and `[error]` attributes have been replaced by CSS custom states (`:state(pressed)`, `:state(icon-only)`, `:state(text-only)`, `:state(error)`). Update any custom styles targeting these attributes.
- The `shape` property type has changed from `ButtonShape` to `ToggleButtonShape` (`'square' | 'pill'`).
- The `pressed` property is no longer reflected as an attribute. Use `:state(pressed)` for styling.
- The `label` property (previously reflected as `aria-label`) has been removed. Use the new `tooltip` property instead.

**New features**

- Added a `tooltip` property for declaratively adding a tooltip. For icon-only buttons the tooltip acts as the accessible label; for other buttons it acts as an accessible description.
- Added CSS parts `button` and `tooltip` for styling the internal elements.
- Focus is now delegated to the internal `<button>` via `delegatesFocus: true`.
- ARIA attributes set on the host are now forwarded to the internal `<button>` via `ForwardAriaMixin`.
5 changes: 5 additions & 0 deletions .changeset/green-ends-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sl-design-system/shared': minor
---

When forwarding ARIA labels or descriptions, the `ForwardAriaMixin` no longer overrides any existing `ariaDescribedByElements` or `ariaLabelledByElements` values. This allows components that use the mixin to maintain their own ARIA relationships without interference, while still forwarding any additional ARIA attributes as needed.
5 changes: 5 additions & 0 deletions .changeset/light-pears-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sl-design-system/button-bar': patch
---

Fix not giving buttons enough time to set the icon-only state
7 changes: 7 additions & 0 deletions .changeset/nasty-clowns-spend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sl-design-system/angular': minor
---

Use the new tooltip implementation

The tooltip directive has been updated to use the new tooltip implementation. This means that the tooltip is now created as a separate element and positioned using the `for` attribute. The old implementation, which used a lazy loader, has been removed. The API of the tooltip directive remains the same, but the DOM structure may have changed.
8 changes: 8 additions & 0 deletions .changeset/public-buckets-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@sl-design-system/avatar': minor
'@sl-design-system/calendar': minor
'@sl-design-system/ellipsize-text': minor
'@sl-design-system/grid': minor
---

Use the new tooltip implementation
7 changes: 7 additions & 0 deletions .changeset/social-cows-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sl-design-system/menu': patch
---

Fix clicking a second time did not dismiss the menu

When a user clicked the menu button to open the menu and then clicked it again to close it, the menu would immediately reopen. This happened because the button click fired after the popover's `toggle` event, causing the button's click handler to call `togglePopover()` again on an already-closed menu. The fix tracks a `#popoverJustClosed` flag via the `beforetoggle` event and skips the click handler when the flag is set.
7 changes: 7 additions & 0 deletions .changeset/tame-insects-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sl-design-system/breadcrumbs': minor
---

Use the new tooltip implementation

The breadcrumbs component has been updated to use the simplified tooltip implementation. Tooltips for truncated breadcrumb links are now managed using the new `<sl-tooltip>` `for` attribute approach, removing the need for manual cleanup functions and reducing internal complexity.
24 changes: 24 additions & 0 deletions .changeset/violet-baboons-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@sl-design-system/tooltip': major
---

Rewrite of the tooltip component: the tooltip was the component responsible for the most bug reports and had a complex internal implementation. This rewrite significantly simplifies the component.

The complex internal positioning logic, `AnchorController`, `EventsController`, and `lazy()` static method have been removed in favour of native browser `popover` and CSS Anchor Positioning APIs.

> [!NOTE]
> CSS Anchor Positioning is not yet supported in all browsers. You may need to include the [CSS Anchor Positioning polyfill](https://anchor-positioning.oddbird.net/) in your application.

#### Breaking changes

- The `TooltipOptions` interface and `Tooltip.lazy()` static method have been removed. Use the `for` attribute to link a tooltip to its anchor instead.
- The `position`, `offset`, `maxWidth`, `arrowPadding`, and `viewportMargin` properties/statics have been removed.
- `hoverShowDelay` changed from `500ms` to `150ms` and `hoverHideDelay` changed from `200ms` to `0ms`.

#### New API

- `for` — links the tooltip to an anchor element by ID
- `type` — controls the ARIA relationship: `'label'` (`ariaLabelledByElements`, default) or `'description'` (`ariaDescribedByElements`)
Comment thread
jpzwarte marked this conversation as resolved.
- `trigger` — space-separated list of triggers: `'focus'`, `'hover'`, and/or `'click'` (default: `'focus hover'`)
Comment thread
jpzwarte marked this conversation as resolved.
- `disabled` — prevents the tooltip from showing
- `open` — reflects the current open state
7 changes: 7 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { type Preview } from '@storybook/web-components-vite';
import MockDate from 'mockdate';
import { type Mode, themes, updateTheme } from './themes.js';

// Load the CSS Anchor Positioning polyfill if needed
if (!('anchorName' in document.documentElement.style)) {
const { default: polyfill } = await import('@oddbird/css-anchor-positioning/shadow');

polyfill();
}

// Load the polyfill for the Invoker API if needed
if (!('command' in HTMLButtonElement.prototype)) {
const { apply } = await import('invokers-polyfill/fn');
Expand Down
8 changes: 0 additions & 8 deletions .storybook/vitest.setup.ts

This file was deleted.

2 changes: 1 addition & 1 deletion chromatic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@storybook/addon-docs": "^10.4.1",
"@storybook/addon-themes": "^10.4.1",
"@storybook/web-components-vite": "^10.4.1",
"lit": "^3.3.2",
"lit": "^3.3.3",
"storybook": "^10.4.1",
"storybook-addon-pseudo-states": "^10.4.1",
"tslib": "^2.8.1",
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,8 @@
"@changesets/get-github-info": "^0.8.0",
"@custom-elements-manifest/analyzer": "^0.11.0",
"@faker-js/faker": "^10.4.0",
"@lit/localize-tools": "^0.8.1",
"@lit/localize-tools": "^0.8.2",
"@oddbird/css-anchor-positioning": "^0.10.0-alpha",
"@playwright/test": "^1.60.0",
"@storybook/addon-a11y": "^10.4.1",
"@storybook/addon-docs": "^10.4.1",
Expand All @@ -418,10 +419,10 @@
"eslint": "^9.27.0",
"husky": "^9.1.7",
"invokers-polyfill": "^1.0.3",
"lint-staged": "^16.4.0",
"lit": "^3.3.2",
"lint-staged": "^17.0.5",
"lit": "^3.3.3",
"mockdate": "^3.0.5",
"oxfmt": "^0.46.0",
"oxfmt": "^0.52.0",
"playwright": "^1.60.0",
"sinon": "^21.1.2",
"sinon-chai": "^4.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"@storybook/angular": "^10.4.1",
"@types/jasmine": "~6.0.0",
"baseline-browser-mapping": "^2.10.33",
"jasmine-core": "~6.1.0",
"jasmine-core": "~6.2.0",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1",
Expand Down
20 changes: 8 additions & 12 deletions packages/angular/src/tooltip.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import '@sl-design-system/tooltip/register.js';
standalone: true
})
export class TooltipDirective implements AfterViewInit, OnChanges, OnDestroy {
private tooltip?: Tooltip | (() => void);
private tooltip?: Tooltip;

@Input() slTooltip = '';

Expand All @@ -27,19 +27,15 @@ export class TooltipDirective implements AfterViewInit, OnChanges, OnDestroy {
}

ngAfterViewInit(): void {
this.tooltip = Tooltip.lazy(this.elRef.nativeElement, tooltip => {
this.tooltip = tooltip;
tooltip.textContent = this.slTooltip;
});
this.tooltip = document.createElement('sl-tooltip');
this.tooltip.for =
this.elRef.nativeElement.id ||= `sl-tooltip-${Math.random().toString(36).slice(2)}`;
this.tooltip.textContent = this.slTooltip;
Comment thread
jpzwarte marked this conversation as resolved.
this.elRef.nativeElement.after(this.tooltip);
}

ngOnDestroy(): void {
if (this.tooltip instanceof Tooltip) {
this.tooltip?.remove();
this.tooltip = undefined;
} else if (this.tooltip) {
this.tooltip();
this.tooltip = undefined;
}
this.tooltip?.remove();
this.tooltip = undefined;
}
}
2 changes: 1 addition & 1 deletion packages/components/announcer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"devDependencies": {
"@lit/localize": "^0.12.2",
"@open-wc/scoped-elements": "^3.0.6",
"lit": "^3.3.2"
"lit": "^3.3.3"
},
"peerDependencies": {
"@lit/localize": "^0.12.1",
Expand Down
6 changes: 5 additions & 1 deletion packages/components/avatar/src/avatar.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { faSchool } from '@fortawesome/pro-regular-svg-icons';
import { type BadgeSize } from '@sl-design-system/badge';
import '@sl-design-system/badge/register.js';
import { Icon } from '@sl-design-system/icon';
import '@sl-design-system/tooltip/register.js';
import { type Meta, type StoryObj } from '@storybook/web-components-vite';
import { type TemplateResult, html, nothing } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
Expand Down Expand Up @@ -103,6 +102,11 @@ export default {
vertical
}) => {
return html`
<style>
sl-avatar::part(tooltip) {
max-inline-size: 200px;
}
</style>
<sl-avatar
.displayName=${displayName}
?image-only=${imageOnly}
Expand Down
37 changes: 20 additions & 17 deletions packages/components/avatar/src/avatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
* picture-url="http://sanomalearning.design/avatars/lynn.png"></sl-avatar>
* ```
*
* @slot - The subheading of the avatar.
* @slot badge - The badge to display on the avatar.
* @slot fallback - The fallback content to display when no picture is set.
*
* @csspart avatar - The container for positioning the badge.
* @csspart initials - The initials to display when no picture is set.
* @csspart name - The display name, either a `<span>` or `<a>` if `href` is set.
* @csspart picture - The element containing the image, initials or fallback content.
* @csspart tooltip - The tooltip that is shown when the display name overflows.
* @csspart wrapper - The wrapper element around the image and name.
*
* @slot badge - The badge to display on the avatar.
* @slot default - The subheading of the avatar.
* @slot fallback - The fallback content to display when no picture is set.
*/
export class Avatar extends ScopedElementsMixin(LitElement) {
/** @internal */
Expand Down Expand Up @@ -117,6 +118,9 @@ export class Avatar extends ScopedElementsMixin(LitElement) {
/** The size of the avatar. */
@property({ reflect: true }) size: AvatarSize = 'md';

/** @internal Whether the tooltip is visible. */
@state() tooltip?: boolean;

/** If true, will display the name below the image. */
@property({ type: Boolean, reflect: true }) vertical?: boolean;

Expand Down Expand Up @@ -147,9 +151,11 @@ export class Avatar extends ScopedElementsMixin(LitElement) {
}

override render(): TemplateResult {
const avatar = this.renderAvatar();

return this.href
? html`<a href=${this.href} part="wrapper">${this.renderAvatar()}</a>`
: html`<div part="wrapper">${this.renderAvatar()}</div>`;
? html`<a href=${this.href} part="wrapper">${avatar}</a>`
: html`<div part="wrapper">${avatar}</div>`;
Comment thread
jpzwarte marked this conversation as resolved.
}

renderAvatar(): TemplateResult {
Expand All @@ -175,8 +181,10 @@ export class Avatar extends ScopedElementsMixin(LitElement) {
${this.imageOnly
? nothing
: html`
<sl-tooltip id="avatar-tooltip">${this.displayName}</sl-tooltip>
<span part="name">${this.displayName}</span>
${this.tooltip
? html`<sl-tooltip for="name" part="tooltip">${this.displayName}</sl-tooltip>`
: nothing}
Comment thread
jpzwarte marked this conversation as resolved.
<span id="name" part="name">${this.displayName}</span>
<slot></slot>
`}
`;
Expand Down Expand Up @@ -216,16 +224,11 @@ export class Avatar extends ScopedElementsMixin(LitElement) {
this.clipPath = undefined;
}

// Check if the name overflows and if so, enable the tooltip
const name = this.renderRoot.querySelector<HTMLElement>('[part="name"]');
if (
name &&
(name?.offsetWidth < name.scrollWidth || name.offsetHeight + 4 < name.scrollHeight)
) {
name.setAttribute('aria-describedby', 'avatar-tooltip');
} else {
name?.removeAttribute('aria-describedby');
}

// Check if the name overflows and if so, enable the tooltip
this.tooltip =
!!name && (name.offsetWidth < name.scrollWidth || name.offsetHeight + 4 < name.scrollHeight);
}

#onSlotChange(event: Event & { target: HTMLSlotElement }): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/components/badge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"test": "echo \"Error: run tests from monorepo root.\" && exit 1"
},
"devDependencies": {
"lit": "^3.3.2"
"lit": "^3.3.3"
},
"peerDependencies": {
"lit": "^3.1.4"
Expand Down
Loading
Loading