Skip to content

Commit fe855ed

Browse files
Improve accessibility and bubble defaults
Update the playground semantics and accessibility coverage, switch the built-in fallback icon to an ellipsis, and prevent secondary-clicks from activating bubbles. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent baaa723 commit fe855ed

26 files changed

Lines changed: 286 additions & 76 deletions

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- A right-click — or any non-primary pointer button — on a bubble no longer activates or drags it.
13+
The press falls through to the browser/host page context menu instead of grabbing the bubble.
14+
15+
### Changed
16+
17+
- The built-in fallback bubble icon (painted only when a bubble has no `icon`, with `bubbleIcon` as
18+
its stroke) is now an ellipsis glyph instead of a chat bubble.
19+
20+
### Internal
21+
22+
- Playground accessibility: segmented controls and accent swatches are now real radio groups,
23+
decorative icons are hidden from assistive tech, shortcut docs use semantic key/action markup,
24+
external links announce that they open a new tab, and the contrast-aware glyph picks its ink by
25+
WCAG contrast ratio.
26+
- Added `@axe-core/playwright` and an end-to-end accessibility scan over the playground.
27+
1028
## [0.5.1] - 2026-06-14
1129

1230
### Fixed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Mounts a bubble. It flies in from the docked side and joins the group. Re-adding
7878
| ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
7979
| `id` | `string` | Unique id for this bubble (required). |
8080
| `label` | `string` | Accessible name for the bubble and its panel, e.g. `"Chat support"`. Without it the bubble announces as a generic button. |
81-
| `icon` | `HTMLElement` | Content shown inside the collapsed bubble (an avatar, an SVG, anything). Defaults to a chat glyph. |
81+
| `icon` | `HTMLElement` | Content shown inside the collapsed bubble (an avatar, an SVG, anything). Defaults to an ellipsis glyph. |
8282
| `content` | `HTMLElement` | Content shown in the expanded panel. Without it the bubble has no panel. |
8383
| `panelWidth` | `number \| string` | Overrides the manager's `panelWidth` for this bubble's panel (px number, or a `"<n>px"` / `"<n>%"` string). |
8484
| `panelMaxHeight` | `number \| string` | Overrides the manager's `panelMaxHeight` for this bubble's panel (px number, or a `"<n>px"` / `"<n>%"` string). |
@@ -180,7 +180,7 @@ Every token the library paints with:
180180
| Token | Paints |
181181
| ---------------- | -------------------------------------------------------------------- |
182182
| `bubbleSurface` | Fill of the collapsed bubble circle |
183-
| `bubbleIcon` | Stroke of the built-in chat glyph (only when a bubble has no `icon`) |
183+
| `bubbleIcon` | Stroke of the built-in ellipsis glyph (only when a bubble has no `icon`) |
184184
| `bubbleShadow` | Drop shadow under each bubble |
185185
| `focusRing` | Ring marking the focused bubble |
186186
| `panelSurface` | Fill of the expanded panel and its caret |

bun.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"node": ">=18"
5858
},
5959
"devDependencies": {
60+
"@axe-core/playwright": "^4.11.3",
6061
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
6162
"@playwright/test": "^1.60.0",
6263
"@sveltejs/vite-plugin-svelte": "^7.1.2",

playground/components/control-segmented.svelte

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,27 @@
1010
value: T;
1111
onSelect: (value: T) => void;
1212
} = $props();
13+
14+
const groupName = $derived(`control-${label.toLowerCase().replaceAll(/\W+/g, "-")}`);
1315
</script>
1416

15-
<div class="flex flex-col gap-1.5">
16-
<span class="text-xs text-zinc-400 light:text-zinc-600">{label}</span>
17+
<fieldset class="flex flex-col gap-1.5">
18+
<legend class="text-xs text-zinc-400 light:text-zinc-600">{label}</legend>
1719
<div class="flex rounded-lg border border-zinc-800 p-0.5 light:border-zinc-300">
1820
{#each options as option (option.value)}
19-
<button
20-
type="button"
21-
aria-pressed={value === option.value}
22-
onclick={() => onSelect(option.value)}
23-
class="focus-ring flex-1 cursor-pointer rounded-md px-2 py-1 text-xs transition-colors {value ===
24-
option.value
25-
? 'bg-zinc-800 text-white light:bg-zinc-900'
26-
: 'text-zinc-400 hover:text-white light:text-zinc-600 light:hover:text-zinc-900'}"
21+
<label
22+
class="relative flex flex-1 cursor-pointer items-center justify-center rounded-md px-2 py-1 text-xs text-zinc-400 transition-colors hover:text-white has-checked:bg-zinc-800 has-checked:text-white has-focus-visible:ring-2 has-focus-visible:ring-zinc-500 has-focus-visible:ring-offset-2 has-focus-visible:ring-offset-black light:text-zinc-600 light:hover:text-zinc-900 light:has-checked:bg-zinc-900 light:has-checked:text-white light:has-focus-visible:ring-zinc-400 light:has-focus-visible:ring-offset-white"
2723
>
24+
<input
25+
type="radio"
26+
name={groupName}
27+
value={option.value}
28+
checked={value === option.value}
29+
onchange={() => onSelect(option.value)}
30+
class="sr-only"
31+
/>
2832
{option.label}
29-
</button>
33+
</label>
3034
{/each}
3135
</div>
32-
</div>
36+
</fieldset>

playground/components/control-slider.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<label class="flex flex-col gap-1.5">
2727
<span class="flex items-baseline justify-between text-xs">
2828
<span class="text-zinc-400 light:text-zinc-600">{label}</span>
29-
<span class="text-zinc-500">{format(shown)}</span>
29+
<span class="text-zinc-400 light:text-zinc-600">{format(shown)}</span>
3030
</span>
3131
<input
3232
type="range"

playground/components/docs-panel.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
66
// The panel renders the actual README — one source of truth. Relative
77
// repo links resolve to GitHub; external links open a new tab.
8+
const externalLinkNote = '<span class="sr-only"> (opens in new tab)</span>';
9+
810
const html = (marked.parse(readme, { async: false }) as string)
911
.replaceAll(
1012
'href="LICENSE"',
1113
'href="https://github.com/githyperplexed/bubbles/blob/main/LICENSE"'
1214
)
13-
.replaceAll('<a href="http', '<a target="_blank" rel="noreferrer" href="http');
15+
.replaceAll('<a href="http', '<a target="_blank" rel="noreferrer" href="http')
16+
.replace(
17+
/<a target="_blank" rel="noreferrer" href="http[^"]+">([\s\S]*?)<\/a>/g,
18+
(anchor) => anchor.replace("</a>", `${externalLinkNote}</a>`)
19+
);
1420
</script>
1521

1622
<div class="flex min-h-0 flex-col font-sans">

playground/components/icons/bubbles-icon.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<!-- The brand mark: three equal overlapping bubbles in one solid ink.
66
Page-colored gap discs under the middle and right circles carve the
77
separation without shrinking any circle's fill. Mirrors public/. -->
8-
<svg viewBox="0 0 48 24" width={size * 2} height={size}>
8+
<svg viewBox="0 0 48 24" width={size * 2} height={size} aria-hidden="true" focusable="false">
99
<circle cx="10" cy="12" r="8.5" fill="currentColor" />
1010
<circle cx="24" cy="12" r="10" class="fill-black light:fill-white" />
1111
<circle cx="24" cy="12" r="8.5" fill="currentColor" />

playground/components/icons/check-icon.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
viewBox="0 0 24 24"
77
width={size}
88
height={size}
9+
aria-hidden="true"
10+
focusable="false"
911
fill="none"
1012
stroke={color}
1113
stroke-width="2"

playground/components/icons/copy-icon.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
viewBox="0 0 24 24"
77
width={size}
88
height={size}
9+
aria-hidden="true"
10+
focusable="false"
911
fill="none"
1012
stroke={color}
1113
stroke-width="2"

0 commit comments

Comments
 (0)