diff --git a/.changeset/a11y-button.md b/.changeset/a11y-button.md new file mode 100644 index 00000000..5040a5fd --- /dev/null +++ b/.changeset/a11y-button.md @@ -0,0 +1,6 @@ +--- +'@surfnet/curve-react': patch +'@surfnet/curve-angular': patch +--- + +Button: the focus indicator is now a solid 2px outline with a 2px gap in `--primary-strong` (`--destructive` for destructive buttons), which meets 3:1 contrast in every theme, in light and dark mode. diff --git a/.changeset/a11y-calendar.md b/.changeset/a11y-calendar.md new file mode 100644 index 00000000..4a2609bd --- /dev/null +++ b/.changeset/a11y-calendar.md @@ -0,0 +1,5 @@ +--- +'@surfnet/curve-angular': patch +--- + +Calendar / Date Picker: day buttons are announced with the full date, and the grid has the month and year as its name, which is also announced when the month changes. diff --git a/.changeset/a11y-combobox.md b/.changeset/a11y-combobox.md new file mode 100644 index 00000000..45a8ef94 --- /dev/null +++ b/.changeset/a11y-combobox.md @@ -0,0 +1,5 @@ +--- +'@surfnet/curve-angular': patch +--- + +Combobox: the input now sets `aria-controls` and `aria-activedescendant`, so screen readers announce the highlighted option. diff --git a/.changeset/a11y-command.md b/.changeset/a11y-command.md new file mode 100644 index 00000000..acb295b4 --- /dev/null +++ b/.changeset/a11y-command.md @@ -0,0 +1,5 @@ +--- +'@surfnet/curve-angular': patch +--- + +Command: the input has a default accessible name, the same as React. diff --git a/.changeset/a11y-context-menu.md b/.changeset/a11y-context-menu.md new file mode 100644 index 00000000..069c3553 --- /dev/null +++ b/.changeset/a11y-context-menu.md @@ -0,0 +1,6 @@ +--- +'@surfnet/curve-react': patch +'@surfnet/curve-angular': patch +--- + +Context Menu: the trigger can be focused and opened with Shift+F10 or the Menu key, and shows the button focus outline. In Angular, focus now returns to the trigger when the menu closes. diff --git a/.changeset/a11y-sonner.md b/.changeset/a11y-sonner.md new file mode 100644 index 00000000..b4fd60f0 --- /dev/null +++ b/.changeset/a11y-sonner.md @@ -0,0 +1,6 @@ +--- +'@surfnet/curve-react': minor +'@surfnet/curve-angular': minor +--- + +Toast (Sonner): toasts now stay 8 seconds (was 4) and show a close button by default. Pass `duration` / `closeButton` to change this. diff --git a/packages/angular/src/lib/ui/button/src/lib/hlm-button.css b/packages/angular/src/lib/ui/button/src/lib/hlm-button.css index d0c9bb5a..16f7e9a8 100644 --- a/packages/angular/src/lib/ui/button/src/lib/hlm-button.css +++ b/packages/angular/src/lib/ui/button/src/lib/hlm-button.css @@ -30,9 +30,11 @@ transform: translateY(1px); } +/* Solid 2px outline with a 2px gap: >= 3:1 against the page in every theme and mode. + --primary-strong is used because --ring is below 3:1 in several themes (see #250). */ .curve-button:focus-visible { - border-color: var(--ring); - box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 32%, transparent); + outline: 2px solid var(--primary-strong); + outline-offset: 2px; } .curve-button:disabled, @@ -135,12 +137,7 @@ } .curve-button--variant-destructive:focus-visible { - border-color: color-mix(in oklch, var(--destructive) 40%, transparent); - box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent); -} - -.dark .curve-button--variant-destructive:focus-visible { - box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent); + outline-color: var(--destructive); } .curve-button--variant-link { diff --git a/packages/angular/src/lib/ui/button/src/lib/hlm-button.stories.ts b/packages/angular/src/lib/ui/button/src/lib/hlm-button.stories.ts index 57263098..ad817030 100644 --- a/packages/angular/src/lib/ui/button/src/lib/hlm-button.stories.ts +++ b/packages/angular/src/lib/ui/button/src/lib/hlm-button.stories.ts @@ -12,8 +12,6 @@ type ButtonArgs = HlmButton & { disabled: boolean }; const meta: Meta = { title: 'Components/Button', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: HlmButton, decorators: [ moduleMetadata({ diff --git a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-multi.ts b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-multi.ts index 199c3775..b47aa6b5 100644 --- a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-multi.ts +++ b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-multi.ts @@ -86,16 +86,20 @@ import type { ClassValue } from 'clsx'; } @case ('dropdown-months') { -
{{ heading.year }}
+ } @case ('dropdown-years') { -
{{ heading.month }}
+ } @case ('label') {
{{ heading.header }}
} } + + @if (captionLayout() !== 'label') { +
{{ heading.header }}
+ }
@@ -134,7 +138,12 @@ import type { ClassValue } from 'clsx'; @for (date of week; track _dateAdapter.getTime(date)) { - @@ -226,4 +235,18 @@ export class HlmCalendarMulti { ); protected readonly _selectClass = 'curve-calendar-select-trigger'; + + /** Full date for screen readers, e.g. "Wednesday, 4 November 2026" (the button only shows "4"). */ + protected _labelDay(date: T): string { + return new Date( + this._dateAdapter.getYear(date), + this._dateAdapter.getMonth(date), + this._dateAdapter.getDate(date), + ).toLocaleDateString(undefined, { + weekday: 'long', + day: 'numeric', + month: 'long', + year: 'numeric', + }); + } } diff --git a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-range.ts b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-range.ts index 8c595378..e7eeca9d 100644 --- a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-range.ts +++ b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar-range.ts @@ -85,16 +85,20 @@ import type { ClassValue } from 'clsx'; } @case ('dropdown-months') { -
{{ heading.year }}
+ } @case ('dropdown-years') { -
{{ heading.month }}
+ } @case ('label') {
{{ heading.header }}
} } + + @if (captionLayout() !== 'label') { +
{{ heading.header }}
+ }
@@ -133,7 +137,12 @@ import type { ClassValue } from 'clsx'; @for (date of week; track _dateAdapter.getTime(date)) { - @@ -218,4 +227,18 @@ export class HlmCalendarRange { ); protected readonly _selectClass = 'curve-calendar-select-trigger'; + + /** Full date for screen readers, e.g. "Wednesday, 4 November 2026" (the button only shows "4"). */ + protected _labelDay(date: T): string { + return new Date( + this._dateAdapter.getYear(date), + this._dateAdapter.getMonth(date), + this._dateAdapter.getDate(date), + ).toLocaleDateString(undefined, { + weekday: 'long', + day: 'numeric', + month: 'long', + year: 'numeric', + }); + } } diff --git a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.css b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.css index c101326e..2e20d487 100644 --- a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.css +++ b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.css @@ -310,3 +310,16 @@ border-radius: calc(infinity * 1px); background-color: var(--destructive); } + +/* Visually hidden month + year heading (screen readers only). */ +.curve-calendar-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} diff --git a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.ts b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.ts index 9584305c..4348c51a 100644 --- a/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.ts +++ b/packages/angular/src/lib/ui/calendar/src/lib/hlm-calendar.ts @@ -84,16 +84,20 @@ import type { ClassValue } from 'clsx'; } @case ('dropdown-months') { -
{{ heading.year }}
+ } @case ('dropdown-years') { -
{{ heading.month }}
+ } @case ('label') {
{{ heading.header }}
} } + + @if (captionLayout() !== 'label') { +
{{ heading.header }}
+ }
@@ -126,7 +130,12 @@ import type { ClassValue } from 'clsx'; @for (date of week; track _dateAdapter.getTime(date)) { - @@ -208,4 +217,18 @@ export class HlmCalendar { ); protected readonly _selectClass = 'curve-calendar-select-trigger'; + + /** Full date for screen readers, e.g. "Wednesday, 4 November 2026" (the button only shows "4"). */ + protected _labelDay(date: T): string { + return new Date( + this._dateAdapter.getYear(date), + this._dateAdapter.getMonth(date), + this._dateAdapter.getDate(date), + ).toLocaleDateString(undefined, { + weekday: 'long', + day: 'numeric', + month: 'long', + year: 'numeric', + }); + } } diff --git a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-chip-input.ts b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-chip-input.ts index 22ff52a5..e9cd8b95 100644 --- a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-chip-input.ts +++ b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-chip-input.ts @@ -1,13 +1,20 @@ import { Directive } from '@angular/core'; import { BrnComboboxChipInput } from '@spartan-ng/brain/combobox'; import { classes } from '../../../utils/src'; +import { injectHlmComboboxAria } from './hlm-combobox-listbox-id'; @Directive({ selector: 'input[hlmComboboxChipInput]', hostDirectives: [{ directive: BrnComboboxChipInput, inputs: ['id', 'aria-invalid'] }], - host: { 'data-slot': 'combobox-chip-input' }, + host: { + 'data-slot': 'combobox-chip-input', + '[attr.aria-controls]': '_aria.controls()', + '[attr.aria-activedescendant]': '_aria.activeDescendant()', + }, }) export class HlmComboboxChipInput { + protected readonly _aria = injectHlmComboboxAria(); + constructor() { classes(() => 'curve-combobox-chip-input'); } diff --git a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-input.ts b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-input.ts index 075989be..418c7aee 100644 --- a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-input.ts +++ b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-input.ts @@ -8,7 +8,7 @@ import { injectBrnComboboxBase, } from '@spartan-ng/brain/combobox'; import { HlmInputGroupImports } from '../../../input-group/src'; -import { injectHlmComboboxListboxId } from './hlm-combobox-listbox-id'; +import { injectHlmComboboxAria, injectHlmComboboxListboxId } from './hlm-combobox-listbox-id'; @Component({ selector: 'hlm-combobox-input', @@ -26,6 +26,8 @@ import { injectHlmComboboxListboxId } from './hlm-combobox-listbox-id'; [placeholder]="placeholder()" [forceInvalid]="forceInvalid()" [aria-invalid]="ariaInvalidOverride()" + [attr.aria-controls]="_aria.controls()" + [attr.aria-activedescendant]="_aria.activeDescendant()" /> @@ -72,6 +74,7 @@ export class HlmComboboxInput { private readonly _combobox = injectBrnComboboxBase(); protected readonly _listboxId = injectHlmComboboxListboxId(); protected readonly _isExpanded = this._combobox.isExpanded; + protected readonly _aria = injectHlmComboboxAria(); public readonly inputId = input(`hlm-combobox-input-${HlmComboboxInput._id++}`); public readonly placeholder = input(''); diff --git a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-listbox-id.ts b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-listbox-id.ts index d313316c..87038c7f 100644 --- a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-listbox-id.ts +++ b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox-listbox-id.ts @@ -1,4 +1,6 @@ -import { inject, InjectionToken, type ValueProvider } from '@angular/core'; +import { computed, inject, InjectionToken, type Signal, type ValueProvider } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { injectBrnComboboxBase } from '@spartan-ng/brain/combobox'; let listboxId = 0; @@ -14,3 +16,27 @@ export function provideHlmComboboxListboxId(): ValueProvider { export function injectHlmComboboxListboxId(): string | null { return inject(HlmComboboxListboxId, { optional: true }); } + +/** + * ARIA links for a combobox input: `aria-controls` points at the open listbox and + * `aria-activedescendant` at the highlighted option, so screen readers announce the + * option while focus stays in the input. Both are `null` while the popup is closed. + */ +export function injectHlmComboboxAria(): { + controls: Signal; + activeDescendant: Signal; +} { + const combobox = injectBrnComboboxBase(); + const listboxId = injectHlmComboboxListboxId(); + // `change` emits the new active index; reading it makes the computed below re-run. + const activeIndex = toSignal(combobox.keyManager.change, { initialValue: -1 }); + + return { + controls: computed(() => (combobox.isExpanded() ? listboxId : null)), + activeDescendant: computed(() => { + activeIndex(); + if (!combobox.isExpanded()) return null; + return combobox.keyManager.activeItem?.id() ?? null; + }), + }; +} diff --git a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox.stories.ts b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox.stories.ts index b89ab579..e3ad4bf5 100644 --- a/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox.stories.ts +++ b/packages/angular/src/lib/ui/combobox/src/lib/hlm-combobox.stories.ts @@ -6,15 +6,14 @@ import { comboboxContract } from '@surfnet/curve-contracts'; import { HlmCombobox } from './hlm-combobox'; import { HlmComboboxImports } from '..'; import { HlmFieldImports } from '../../../field/src'; +import { HlmLabelImports } from '../../../label/src'; const meta: Meta = { title: 'Components/Combobox', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: HlmCombobox, decorators: [ moduleMetadata({ - imports: [HlmComboboxImports, HlmFieldImports, ReactiveFormsModule], + imports: [HlmComboboxImports, HlmFieldImports, HlmLabelImports, ReactiveFormsModule], }), ], parameters: { @@ -37,8 +36,10 @@ type Story = StoryObj; export const Default: Story = { render: () => ({ template: ` - - +
+ + + No countries found.
@@ -55,6 +56,7 @@ export const Default: Story = {
+
`, }), }; @@ -63,8 +65,10 @@ export const Default: Story = { export const Grouped: Story = { render: () => ({ template: ` - - +
+ + + No frameworks found.
@@ -86,34 +90,38 @@ export const Grouped: Story = {
+
`, }), }; @Component({ selector: 'combobox-multiple-demo', - imports: [HlmComboboxImports], + imports: [HlmComboboxImports, HlmLabelImports], template: ` - - - - @for (value of values; track value) { - {{ value }} - } - - - - - No languages found. -
- TypeScript - JavaScript - Python - Rust - Go -
-
-
+
+ + + + + @for (value of values; track value) { + {{ value }} + } + + + + + No languages found. +
+ TypeScript + JavaScript + Python + Rust + Go +
+
+
+
`, }) class ComboboxMultipleDemo { @@ -134,12 +142,15 @@ export const Multiple: Story = { export const Disabled: Story = { render: () => ({ template: ` - - +
+ + + Netherlands +
`, }), }; diff --git a/packages/angular/src/lib/ui/command/src/lib/hlm-command-input.ts b/packages/angular/src/lib/ui/command/src/lib/hlm-command-input.ts index 1679cac1..21718765 100644 --- a/packages/angular/src/lib/ui/command/src/lib/hlm-command-input.ts +++ b/packages/angular/src/lib/ui/command/src/lib/hlm-command-input.ts @@ -32,8 +32,10 @@ export class HlmCommandInput { public readonly inputId = input(); public readonly placeholder = input(''); - /** Accessible name applied to the underlying combobox input. */ - public readonly ariaLabel = input(null, { alias: 'aria-label' }); + /** Accessible name applied to the underlying combobox input. Same default as React. */ + public readonly ariaLabel = input('Type a command or search...', { + alias: 'aria-label', + }); /** Accessible name applied to the underlying combobox input via a visible label. */ public readonly ariaLabelledby = input(null, { alias: 'aria-labelledby' }); diff --git a/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu-trigger.ts b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu-trigger.ts index 9561dd20..fd6d8df0 100644 --- a/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu-trigger.ts +++ b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu-trigger.ts @@ -1,6 +1,14 @@ import { type BooleanInput } from '@angular/cdk/coercion'; import { CdkContextMenuTrigger } from '@angular/cdk/menu'; -import { booleanAttribute, computed, Directive, effect, inject, input } from '@angular/core'; +import { + booleanAttribute, + computed, + Directive, + effect, + ElementRef, + inject, + input, +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { createMenuPosition, type MenuAlign, type MenuSide } from '@spartan-ng/brain/core'; import { injectHlmContextMenuConfig } from './hlm-context-menu-token'; @@ -23,10 +31,14 @@ import { injectHlmContextMenuConfig } from './hlm-context-menu-token'; ], host: { 'data-slot': 'context-menu-trigger', + // Focusable so keyboard users can open the menu with Shift+F10 or the Menu key (WCAG 2.1.1). + '[attr.tabindex]': 'disabled() ? null : 0', + '(keydown)': '_onKeydown($event)', '[attr.data-disabled]': 'disabled() ? "" : null', }, }) export class HlmContextMenuTrigger { + private readonly _host = inject>(ElementRef); private readonly _cdkTrigger = inject(CdkContextMenuTrigger, { host: true }); private readonly _config = injectHlmContextMenuConfig(); @@ -39,6 +51,26 @@ export class HlmContextMenuTrigger { private readonly _menuPosition = computed(() => createMenuPosition(this.align(), this.side())); + /** + * Shift+F10 or the Menu key opens the menu. Not every browser fires `contextmenu` for these + * keys, so fire it ourselves, positioned at the bottom-left corner of the trigger. + */ + protected _onKeydown(event: KeyboardEvent): void { + const isMenuKey = event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10'); + if (event.defaultPrevented || !isMenuKey || this.disabled()) return; + event.preventDefault(); + const element = this._host.nativeElement; + const rect = element.getBoundingClientRect(); + element.dispatchEvent( + new MouseEvent('contextmenu', { + bubbles: true, + cancelable: true, + clientX: rect.left, + clientY: rect.bottom, + }), + ); + } + constructor() { // once the trigger opens we wait until the next tick and then grab the last position // used to position the menu. we store this in our trigger which the brnMenu directive has @@ -55,5 +87,14 @@ export class HlmContextMenuTrigger { effect(() => { this._cdkTrigger.menuPosition = this._menuPosition(); }); + + // Return focus to the trigger when the menu closes and focus was left on the page body + // (WCAG 2.4.3), as React's Base UI context menu does. + this._cdkTrigger.closed.pipe(takeUntilDestroyed()).subscribe(() => + setTimeout(() => { + const active = document.activeElement; + if (!active || active === document.body) this._host.nativeElement.focus(); + }), + ); } } diff --git a/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.css b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.css new file mode 100644 index 00000000..acd7ff27 --- /dev/null +++ b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.css @@ -0,0 +1,5 @@ +/* Same focus outline as Button: the trigger is focusable so keyboard users can open the menu. */ +[data-slot='context-menu-trigger']:focus-visible { + outline: 2px solid var(--primary-strong); + outline-offset: 2px; +} diff --git a/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.stories.ts b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.stories.ts index dcbaca33..2b8431b7 100644 --- a/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.stories.ts +++ b/packages/angular/src/lib/ui/context-menu/src/lib/hlm-context-menu.stories.ts @@ -5,8 +5,6 @@ import { HlmDropdownMenuImports } from '../../../dropdown-menu/src'; const meta: Meta = { title: 'Components/ContextMenu', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: HlmContextMenuTrigger, decorators: [ moduleMetadata({ @@ -37,7 +35,7 @@ export const Default: Story = { render: () => ({ template: `
- Right click here + Right-click here, or focus and press Shift+F10
@@ -45,7 +43,6 @@ export const Default: Story = { My Account @@ -62,7 +59,7 @@ export const WithCheckboxItems: Story = { render: () => ({ template: `
- Right click here + Right-click here, or focus and press Shift+F10
@@ -90,7 +87,7 @@ export const WithRadioItems: Story = { render: () => ({ template: `
- Right click here + Right-click here, or focus and press Shift+F10
@@ -118,7 +115,7 @@ export const WithSubmenu: Story = { render: () => ({ template: `
- Right click here + Right-click here, or focus and press Shift+F10
diff --git a/packages/angular/src/lib/ui/date-picker/src/lib/hlm-date-picker.stories.ts b/packages/angular/src/lib/ui/date-picker/src/lib/hlm-date-picker.stories.ts index e9f8bb4d..e7360e1f 100644 --- a/packages/angular/src/lib/ui/date-picker/src/lib/hlm-date-picker.stories.ts +++ b/packages/angular/src/lib/ui/date-picker/src/lib/hlm-date-picker.stories.ts @@ -8,8 +8,6 @@ import { HlmFieldImports } from '../../../field/src'; const meta: Meta> = { title: 'Components/DatePicker', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: HlmDatePicker, decorators: [ moduleMetadata({ diff --git a/packages/angular/src/lib/ui/input-otp/src/lib/hlm-input-otp.stories.ts b/packages/angular/src/lib/ui/input-otp/src/lib/hlm-input-otp.stories.ts index d3bca152..5a560e3c 100644 --- a/packages/angular/src/lib/ui/input-otp/src/lib/hlm-input-otp.stories.ts +++ b/packages/angular/src/lib/ui/input-otp/src/lib/hlm-input-otp.stories.ts @@ -12,8 +12,6 @@ type InputOtpArgs = HlmInputOtp & { maxLength: number; disabled: boolean }; const meta: Meta = { title: 'Components/InputOTP', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: HlmInputOtp, decorators: [ moduleMetadata({ diff --git a/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.stories.ts b/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.stories.ts index f6c298c5..d498a981 100644 --- a/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.stories.ts +++ b/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.stories.ts @@ -8,8 +8,6 @@ import { HlmToaster, HlmToasterImports } from '..'; const meta: Meta = { title: 'Components/Sonner', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: HlmToaster, decorators: [ moduleMetadata({ @@ -56,7 +54,7 @@ const meta: Meta = { args: { position: 'bottom-right', richColors: false, - closeButton: false, + closeButton: true, expand: false, }, }; diff --git a/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.ts b/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.ts index cb330240..ef549bb7 100644 --- a/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.ts +++ b/packages/angular/src/lib/ui/sonner/src/lib/hlm-toaster.ts @@ -79,13 +79,13 @@ export class HlmToaster { public readonly expand = input(false, { transform: booleanAttribute, }); - public readonly duration = input(4000, { + public readonly duration = input(8000, { transform: numberAttribute, }); public readonly visibleToasts = input(3, { transform: numberAttribute, }); - public readonly closeButton = input(false, { + public readonly closeButton = input(true, { transform: booleanAttribute, }); public readonly toastOptions = input({}); diff --git a/packages/angular/src/styles.css b/packages/angular/src/styles.css index 2ad06ab6..3ea58a50 100644 --- a/packages/angular/src/styles.css +++ b/packages/angular/src/styles.css @@ -60,6 +60,7 @@ @import './lib/ui/carousel/src/lib/hlm-carousel.css' layer(components); @import './lib/ui/combobox/src/lib/hlm-combobox.css' layer(components); @import './lib/ui/command/src/lib/hlm-command.css' layer(components); +@import './lib/ui/context-menu/src/lib/hlm-context-menu.css' layer(components); @import './lib/ui/curve-data-table/src/lib/curve-data-table.css' layer(components); @import './lib/ui/data-table/src/lib/hlm-data-table.css' layer(components); @import './lib/ui/date-picker/src/lib/hlm-date-picker.css' layer(components); diff --git a/packages/react/src/components/ui/button/button.module.css b/packages/react/src/components/ui/button/button.module.css index cdba4cdf..77d98758 100644 --- a/packages/react/src/components/ui/button/button.module.css +++ b/packages/react/src/components/ui/button/button.module.css @@ -20,9 +20,11 @@ -webkit-tap-highlight-color: transparent; } +/* Solid 2px outline with a 2px gap: >= 3:1 against the page in every theme and mode. + --primary-strong is used because --ring is below 3:1 in several themes (see #250). */ .button:focus-visible { - border-color: var(--ring); - box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 32%, transparent); + outline: 2px solid var(--primary-strong); + outline-offset: 2px; } .button:active:not([aria-haspopup]) { @@ -132,12 +134,7 @@ } .variantDestructive:focus-visible { - border-color: color-mix(in oklch, var(--destructive) 40%, transparent); - box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 20%, transparent); -} - -:global(.dark) .variantDestructive:focus-visible { - box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive) 40%, transparent); + outline-color: var(--destructive); } .variantLink { diff --git a/packages/react/src/components/ui/button/button.stories.tsx b/packages/react/src/components/ui/button/button.stories.tsx index 7d4c2a67..d2fd6e9c 100644 --- a/packages/react/src/components/ui/button/button.stories.tsx +++ b/packages/react/src/components/ui/button/button.stories.tsx @@ -6,8 +6,6 @@ import { Button } from './button'; const meta = { title: 'Components/Button', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: Button, parameters: { docs: { diff --git a/packages/react/src/components/ui/combobox/combobox.stories.tsx b/packages/react/src/components/ui/combobox/combobox.stories.tsx index 12ba6500..9c76215d 100644 --- a/packages/react/src/components/ui/combobox/combobox.stories.tsx +++ b/packages/react/src/components/ui/combobox/combobox.stories.tsx @@ -19,6 +19,7 @@ import { ComboboxValue, useComboboxAnchor, } from './combobox'; +import { Label } from '@/components/ui/label'; const countries = [ 'Belgium', @@ -42,8 +43,6 @@ const languages = ['TypeScript', 'JavaScript', 'Python', 'Rust', 'Go']; const meta = { title: 'Components/Combobox', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: Combobox, parameters: { docs: { @@ -65,48 +64,59 @@ type Story = StoryObj; /** A searchable, filterable select — type to filter, with a trigger and clear button. */ export const Default: Story = { render: () => ( - - - - No countries found. - - {(item: string) => ( - - {item} - - )} - - - +
+ + + + + No countries found. + + {(item: string) => ( + + {item} + + )} + + + +
), }; /** Options organized into labelled, independently-filtered groups. */ export const Grouped: Story = { render: () => ( - - - - No frameworks found. - - {(group: (typeof frameworkGroups)[number], index: number) => ( - - {index > 0 && } - - {group.value} - - {(item: string) => ( - - {item} - - )} - - - - )} - - - +
+ + + + + No frameworks found. + + {(group: (typeof frameworkGroups)[number], index: number) => ( + + {index > 0 && } + + {group.value} + + {(item: string) => ( + + {item} + + )} + + + + )} + + + +
), }; @@ -115,25 +125,54 @@ export const Multiple: Story = { render: function MultipleCombobox() { const anchor = useComboboxAnchor(); return ( - -
- - - {(value: string[]) => ( - <> - {value.map((language) => ( - - {language} - - ))} - - +
+ + +
+ + + {(value: string[]) => ( + <> + {value.map((language) => ( + + {language} + + ))} + + + )} + + +
+ + No languages found. + + {(item: string) => ( + + {item} + )} - - -
- - No languages found. + + + +
+ ); + }, +}; + +/** Disabled state — the input, trigger, and clear button all ignore interaction. */ +export const Disabled: Story = { + render: () => ( +
+ + + + {(item: string) => ( @@ -143,24 +182,6 @@ export const Multiple: Story = { - ); - }, -}; - -/** Disabled state — the input, trigger, and clear button all ignore interaction. */ -export const Disabled: Story = { - render: () => ( - - - - - {(item: string) => ( - - {item} - - )} - - - +
), }; diff --git a/packages/react/src/components/ui/context-menu/context-menu.module.css b/packages/react/src/components/ui/context-menu/context-menu.module.css index 66c149a3..96ca7c6d 100644 --- a/packages/react/src/components/ui/context-menu/context-menu.module.css +++ b/packages/react/src/components/ui/context-menu/context-menu.module.css @@ -2,6 +2,12 @@ user-select: none; } +/* Same focus outline as Button. */ +.trigger:focus-visible { + outline: 2px solid var(--primary-strong); + outline-offset: 2px; +} + .positioner { isolation: isolate; z-index: 50; diff --git a/packages/react/src/components/ui/context-menu/context-menu.stories.tsx b/packages/react/src/components/ui/context-menu/context-menu.stories.tsx index c603e32a..79d6c34c 100644 --- a/packages/react/src/components/ui/context-menu/context-menu.stories.tsx +++ b/packages/react/src/components/ui/context-menu/context-menu.stories.tsx @@ -11,7 +11,6 @@ import { ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, - ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, @@ -20,8 +19,6 @@ import { const meta = { title: 'Components/ContextMenu', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: ContextMenu, parameters: { docs: { @@ -47,14 +44,13 @@ const triggerAreaClassName = export const Default: Story = { render: () => ( - Right click here + + Right-click here, or focus and press Shift+F10 + My account - - Profile - ⇧⌘P - + Profile Settings @@ -68,7 +64,9 @@ export const Default: Story = { export const WithCheckboxItems: Story = { render: () => ( - Right click here + + Right-click here, or focus and press Shift+F10 + Toggle columns @@ -85,7 +83,9 @@ export const WithCheckboxItems: Story = { export const WithRadioItems: Story = { render: () => ( - Right click here + + Right-click here, or focus and press Shift+F10 + View @@ -102,7 +102,9 @@ export const WithRadioItems: Story = { export const WithSubmenu: Story = { render: () => ( - Right click here + + Right-click here, or focus and press Shift+F10 + Back Forward diff --git a/packages/react/src/components/ui/context-menu/context-menu.tsx b/packages/react/src/components/ui/context-menu/context-menu.tsx index f2ea4b7b..96a7b753 100644 --- a/packages/react/src/components/ui/context-menu/context-menu.tsx +++ b/packages/react/src/components/ui/context-menu/context-menu.tsx @@ -17,11 +17,44 @@ function ContextMenuPortal({ ...props }: ContextMenuPrimitive.Portal.Props) { return ; } -function ContextMenuTrigger({ className, ...props }: ContextMenuPrimitive.Trigger.Props) { +/** Shift+F10 or the Menu key: the keyboard way to open a context menu. */ +function isContextMenuKey(event: React.KeyboardEvent) { + return event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10'); +} + +/** + * Focusable by default (`tabIndex={0}`) so keyboard users can open the menu with Shift+F10 + * or the Menu key (WCAG 2.1.1). Offer the same actions somewhere visible as well. + */ +function ContextMenuTrigger({ + className, + tabIndex = 0, + onKeyDown, + ...props +}: ContextMenuPrimitive.Trigger.Props) { + const handleKeyDown: ContextMenuPrimitive.Trigger.Props['onKeyDown'] = (event) => { + onKeyDown?.(event); + if (event.defaultPrevented || !isContextMenuKey(event)) return; + // Not every browser fires `contextmenu` for these keys, so fire it ourselves, + // positioned at the bottom-left corner of the trigger. + event.preventDefault(); + const rect = event.currentTarget.getBoundingClientRect(); + event.currentTarget.dispatchEvent( + new MouseEvent('contextmenu', { + bubbles: true, + cancelable: true, + clientX: rect.left, + clientY: rect.bottom, + }), + ); + }; + return ( ); @@ -198,6 +231,7 @@ function ContextMenuSeparator({ className, ...props }: ContextMenuPrimitive.Sepa ); } +/** Only show a shortcut that really works, and set `aria-keyshortcuts` on its item. */ function ContextMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) { return ( diff --git a/packages/react/src/components/ui/date-picker/date-picker.stories.tsx b/packages/react/src/components/ui/date-picker/date-picker.stories.tsx index 40612740..265fa962 100644 --- a/packages/react/src/components/ui/date-picker/date-picker.stories.tsx +++ b/packages/react/src/components/ui/date-picker/date-picker.stories.tsx @@ -9,8 +9,6 @@ import { DatePickerInput } from './date-picker-input'; const meta = { title: 'Components/DatePicker', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: DatePicker, parameters: { docs: { diff --git a/packages/react/src/components/ui/input-otp/input-otp.stories.tsx b/packages/react/src/components/ui/input-otp/input-otp.stories.tsx index 02bf9404..abe3a069 100644 --- a/packages/react/src/components/ui/input-otp/input-otp.stories.tsx +++ b/packages/react/src/components/ui/input-otp/input-otp.stories.tsx @@ -10,8 +10,6 @@ import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from './inpu const meta = { title: 'Components/InputOTP', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-gap'], component: InputOTP, parameters: { docs: { diff --git a/packages/react/src/components/ui/sonner/sonner.stories.tsx b/packages/react/src/components/ui/sonner/sonner.stories.tsx index d12e65ae..857cb8a6 100644 --- a/packages/react/src/components/ui/sonner/sonner.stories.tsx +++ b/packages/react/src/components/ui/sonner/sonner.stories.tsx @@ -8,8 +8,6 @@ import { Toaster } from './sonner'; const meta = { title: 'Components/Sonner', - // Unverified: shadcn/ui WCAG 2.2 AA audit (thefrontkit, 2026). - tags: ['a11y-minor'], component: Toaster, parameters: { docs: { @@ -51,7 +49,7 @@ const meta = { args: { position: 'bottom-right', richColors: false, - closeButton: false, + closeButton: true, expand: false, }, } satisfies Meta; diff --git a/packages/react/src/components/ui/sonner/sonner.tsx b/packages/react/src/components/ui/sonner/sonner.tsx index 96ee0fc5..05cf8213 100644 --- a/packages/react/src/components/ui/sonner/sonner.tsx +++ b/packages/react/src/components/ui/sonner/sonner.tsx @@ -12,13 +12,19 @@ import { Toaster as Sonner, type ToasterProps } from 'sonner'; import styles from './sonner.module.css'; -const Toaster = ({ ...props }: ToasterProps) => { +/** + * Defaults follow WCAG 2.2.1 (Timing Adjustable): toasts stay 8 seconds and always have a + * close button. Use `duration: Infinity` for messages the user must act on. + */ +const Toaster = ({ duration = 8000, closeButton = true, ...props }: ToasterProps) => { const { theme = 'system' } = useTheme(); return ( , info: , diff --git a/tests/visual/__screenshots__/angular/components-combobox--default-dark.png b/tests/visual/__screenshots__/angular/components-combobox--default-dark.png index 88816000..fd0fe453 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--default-dark.png and b/tests/visual/__screenshots__/angular/components-combobox--default-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--default-light.png b/tests/visual/__screenshots__/angular/components-combobox--default-light.png index b29c8990..2f03210c 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--default-light.png and b/tests/visual/__screenshots__/angular/components-combobox--default-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--disabled-dark.png b/tests/visual/__screenshots__/angular/components-combobox--disabled-dark.png index 0e27bd87..bb66d6f0 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--disabled-dark.png and b/tests/visual/__screenshots__/angular/components-combobox--disabled-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--disabled-light.png b/tests/visual/__screenshots__/angular/components-combobox--disabled-light.png index e32b0ce7..d063edaa 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--disabled-light.png and b/tests/visual/__screenshots__/angular/components-combobox--disabled-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--grouped-dark.png b/tests/visual/__screenshots__/angular/components-combobox--grouped-dark.png index c0adc049..f76c1f19 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--grouped-dark.png and b/tests/visual/__screenshots__/angular/components-combobox--grouped-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--grouped-light.png b/tests/visual/__screenshots__/angular/components-combobox--grouped-light.png index 6eca3253..a0b4a7d6 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--grouped-light.png and b/tests/visual/__screenshots__/angular/components-combobox--grouped-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--multiple-dark.png b/tests/visual/__screenshots__/angular/components-combobox--multiple-dark.png index 788b690e..792d8a85 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--multiple-dark.png and b/tests/visual/__screenshots__/angular/components-combobox--multiple-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-combobox--multiple-light.png b/tests/visual/__screenshots__/angular/components-combobox--multiple-light.png index beae9e04..f4b7daca 100644 Binary files a/tests/visual/__screenshots__/angular/components-combobox--multiple-light.png and b/tests/visual/__screenshots__/angular/components-combobox--multiple-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--default-dark.png b/tests/visual/__screenshots__/angular/components-contextmenu--default-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--default-dark.png and b/tests/visual/__screenshots__/angular/components-contextmenu--default-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--default-light.png b/tests/visual/__screenshots__/angular/components-contextmenu--default-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--default-light.png and b/tests/visual/__screenshots__/angular/components-contextmenu--default-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-dark.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-dark.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-light.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-light.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-checkbox-items-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-dark.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-dark.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-light.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-light.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-radio-items-light.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-dark.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-dark.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-dark.png differ diff --git a/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-light.png b/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-light.png and b/tests/visual/__screenshots__/angular/components-contextmenu--with-submenu-light.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--default-dark.png b/tests/visual/__screenshots__/react/components-combobox--default-dark.png index 7911100f..5f0dd85e 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--default-dark.png and b/tests/visual/__screenshots__/react/components-combobox--default-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--default-light.png b/tests/visual/__screenshots__/react/components-combobox--default-light.png index 1d282746..27a785dc 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--default-light.png and b/tests/visual/__screenshots__/react/components-combobox--default-light.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--disabled-dark.png b/tests/visual/__screenshots__/react/components-combobox--disabled-dark.png index 5ba688b9..11addde5 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--disabled-dark.png and b/tests/visual/__screenshots__/react/components-combobox--disabled-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--disabled-light.png b/tests/visual/__screenshots__/react/components-combobox--disabled-light.png index a644dd81..ae1ebbaa 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--disabled-light.png and b/tests/visual/__screenshots__/react/components-combobox--disabled-light.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--grouped-dark.png b/tests/visual/__screenshots__/react/components-combobox--grouped-dark.png index ed9f110c..015ca081 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--grouped-dark.png and b/tests/visual/__screenshots__/react/components-combobox--grouped-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--grouped-light.png b/tests/visual/__screenshots__/react/components-combobox--grouped-light.png index 8a9ff929..3d5355f0 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--grouped-light.png and b/tests/visual/__screenshots__/react/components-combobox--grouped-light.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--multiple-dark.png b/tests/visual/__screenshots__/react/components-combobox--multiple-dark.png index 9b630875..9e621c78 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--multiple-dark.png and b/tests/visual/__screenshots__/react/components-combobox--multiple-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-combobox--multiple-light.png b/tests/visual/__screenshots__/react/components-combobox--multiple-light.png index 45a79ca9..cbbe8eff 100644 Binary files a/tests/visual/__screenshots__/react/components-combobox--multiple-light.png and b/tests/visual/__screenshots__/react/components-combobox--multiple-light.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--default-dark.png b/tests/visual/__screenshots__/react/components-contextmenu--default-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--default-dark.png and b/tests/visual/__screenshots__/react/components-contextmenu--default-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--default-light.png b/tests/visual/__screenshots__/react/components-contextmenu--default-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--default-light.png and b/tests/visual/__screenshots__/react/components-contextmenu--default-light.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-dark.png b/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-dark.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-light.png b/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-light.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-checkbox-items-light.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-dark.png b/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-dark.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-light.png b/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-light.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-radio-items-light.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-dark.png b/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-dark.png index 5a4f543b..e94b0d5a 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-dark.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-dark.png differ diff --git a/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-light.png b/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-light.png index ed89900e..d0477268 100644 Binary files a/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-light.png and b/tests/visual/__screenshots__/react/components-contextmenu--with-submenu-light.png differ