Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
15d5e0b
OO-62923 CRITICAL bits a11y issues
ludmilaFialova May 22, 2026
5c47cd8
OO-62923 l18n and IMPORTANT bits a11y issues
ludmilaFialova May 25, 2026
eebca5a
OO-62923 CRITICAL dashboard a11y issues
ludmilaFialova May 25, 2026
dd8b36b
OO-62923 IMPORTANT dashboard a11y issues
ludmilaFialova May 25, 2026
b83d591
OO-62923 charts a11y issues
ludmilaFialova May 22, 2026
4cd831a
OO-62923 Percy and bits-ui-test fix
ludmilaFialova May 26, 2026
b5964d1
OO-62923 CRITICAL a11y fixed round 2
ludmilaFialova May 27, 2026
7471efe
OO-62923 IMPORTANT a11y fixed round 2
ludmilaFialova May 27, 2026
481c196
OO-62923 The tabs in nui-tab-group are now fully keyboard-accessible
ludmilaFialova May 28, 2026
558a3f1
OO-62923 button click animation same for mouse and keyboard
ludmilaFialova May 27, 2026
e014fab
OO-62923 A11y unit tests
ludmilaFialova May 28, 2026
3438ec5
OO-62923 table widget and component
ludmilaFialova Jun 5, 2026
c9ef187
OO-62923 charts, dashboards and readme fix
ludmilaFialova Jun 5, 2026
d412c1f
OO-62923 a11y fix of bits round 3
ludmilaFialova Jun 8, 2026
9c8189a
OO-62923 fix of e2e tests
ludmilaFialova Jun 8, 2026
2053591
NUI-6103 .focus-outline()
ludmilaFialova Jun 9, 2026
9b6b563
OO-62923 a11y fix of bits round 4
ludmilaFialova Jun 9, 2026
2277321
OO-62923 Custom ID for translation of Angular i18n
ludmilaFialova Jun 9, 2026
fd5edfc
OO-62923 e2e tests fix
ludmilaFialova Jun 9, 2026
caa51b3
OO-62923 dialog in overlay a11y fix
ludmilaFialova Jun 10, 2026
3658e1b
OO-62923 agent
ludmilaFialova May 22, 2026
b5adde6
OO-62923 a11y fix of bits round 5
ludmilaFialova Jun 12, 2026
50de989
bits-verify fixed
ludmilaFialova Aug 5, 2026
bb3f43c
OO-62923 SkipSpace issue fixed
ludmilaFialova Aug 10, 2026
af02690
bits-ui-tests errors fixed
ludmilaFialova Sep 22, 2026
1edd94e
fix(progress): remove duplicate progressbar role and help id
ludmilaFialova Sep 22, 2026
cc5c37e
fix(expander): make the body region landmark opt-in (OO-50773)
ludmilaFialova Sep 23, 2026
07592bf
Merge remote-tracking branch 'origin/main' into A11y_fix_from_agent_i…
ludmilaFialova Sep 29, 2026
b673bea
test(chips): wait for chip removal in ChipsAtom.removeItem
ludmilaFialova Sep 30, 2026
c115319
test(e2e): stabilize visual snapshots and cover animations functionally
ludmilaFialova Sep 30, 2026
8a38e8b
chore(a11y): address PR review checklist
ludmilaFialova Oct 1, 2026
b86d3e3
fix(dialog): label dialog by its header title automatically
ludmilaFialova Oct 5, 2026
7162a79
test(e2e): stabilize Percy visual snapshots
ludmilaFialova Oct 5, 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
18 changes: 15 additions & 3 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## [21.0.5] 📅 2026-10-01

### Added

- `@nova-ui/bits` | **nui-overlay** new inputs `trapFocus`, `idAttr`, `ariaDescribedby` and `ariaModal`
- `@nova-ui/bits` | **nui-dialog** new inputs `ariaLabel`, `ariaLabelledby` and `ariaDescribedby`
- `@nova-ui/bits` | **nui-expander** new input `useRegionLandmark`; the body is no longer a `region` landmark by default

### Fixes

- `@nova-ui/bits` | SkipSpace a11y regression fixed
- `@nova-ui/bits` | A11y fixes for nui-progress, nui-dialog, nui-textbox-number, nui-tab-group, nui-message, nui-toast, nui-range-filter, nui-date-picker and nui-menu
- `@nova-ui/bits` | **nui-dialog** is labelled by its **nui-dialog-header** title automatically
- `@nova-ui/dashboards` | Keyboard access for KPI tiles, risk score tiles and table widget rows

## [21.0.4] 📅 2026-09-16

### Added
Expand All @@ -9,11 +24,8 @@

### Fixes

- `@nova-ui/bits` | A11y fixies for Home Summary
- `@nova-ui/bits` | A11y fixies for Search component
- `@nova-ui/bits` | new accessible blue for active menu button text
- Essentional A11y fixies
- `@nova-ui/bits` | Added unique IDs to expander landmarks
- `@nova-ui/bits` | A11y fixes for nui-toolbar
- Prettier fix

Expand Down
19 changes: 14 additions & 5 deletions docs/E2E/VISUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Goals:
## Setup

- Playwright config at `packages/bits/playwright.config.ts` defines a `visual` project and outputs artifacts to `test-results/`.
- Manual snapshots: set `SNAPSHOTS_UPLOAD=manual` to write PNGs to `_snapshots/`.
- Percy (optional): set `PERCY_TOKEN` in your environment and run via `percy exec`.
- Without `PERCY_TOKEN` (default, also in CI): the Eyes lens saves full-page Playwright screenshots as PNGs to `_snapshots/`.
- With `PERCY_TOKEN`: the Percy lens sends DOM snapshots via `@percy/playwright` (run via `percy exec`); `PERCY_DEFAULT_CONFIG` (widths, `percyCSS`) applies only here.

## Virtual Camera API

Expand All @@ -27,12 +27,21 @@ Import from `packages/bits/e2e/virtual-camera`:
- `camera.say.cheese(label)` – capture a snapshot with an optional stabilization delay
- `camera.be.responsive(widths, callback?)` – set responsive widths and optional callback that receives the Playwright `page`

By default, the Camera engine selects the Percy lens when `process.env.PERCY` is present. Otherwise, with `SNAPSHOTS_UPLOAD=manual`, it saves PNGs.
The Camera engine selects the Percy lens only when `process.env.PERCY_TOKEN` is set; otherwise it uses the Eyes lens (PNG screenshots).

### How CI produces Percy snapshots

CircleCI runs the UI tests **without** `PERCY_TOKEN`, so every snapshot is a Playwright screenshot written to `_snapshots/` by the Eyes lens. A later step uploads these images with `percy upload _snapshots`. Consequences:

- Percy does not re-render the page; `percyCSS` and other Percy lens options have no effect in CI.
- Snapshot stability must be ensured before/while the screenshot is taken:
- `camera.say.cheese()` loads font faces used by visible text, then waits for font and layout work to settle before taking the screenshot.
- The Eyes lens takes screenshots with `animations: "disabled"` and `caret: "hide"`, so infinite CSS animations (spinners, progress bars) are frozen.
- To reproduce CI snapshots locally, run the visual tests without `PERCY_TOKEN` and inspect the PNGs in `_snapshots/`.

## Best Practices

- Disable CSS animations for visual runs:
- `await Helpers.disableCSSAnimations(Animations.TRANSITIONS_AND_ANIMATIONS)`
- Animations are frozen by the camera at screenshot time. `Helpers.disableCSSAnimations(...)` injects its styles via `page.addInitScript`, so it only affects pages loaded after the call (not the page already opened by `Helpers.prepareBrowser`).
- Interact via Atoms (see `docs/E2E/ATOMS.md`) to keep tests resilient.
- Use meaningful snapshot labels tied to user actions.
- Prefer consistent viewport widths; set `camera.be.responsive([1920])` as needed.
Expand Down
4 changes: 4 additions & 0 deletions packages/bits/demo/src/components/demo/demo.files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ export const DEMO_PATHS = [
"dialog/component-as-content/dialog-content.example.component.ts",
"dialog/confirmation-dialog/confirmation-dialog.example.component.html",
"dialog/confirmation-dialog/confirmation-dialog.example.component.ts",
"dialog/dialog-accessibility/dialog-accessibility.example.component.html",
"dialog/dialog-accessibility/dialog-accessibility.example.component.ts",
"dialog/dialog-actions-before-closure/dialog-actions-before-closure.example.component.html",
"dialog/dialog-actions-before-closure/dialog-actions-before-closure.example.component.ts",
"dialog/dialog-after-opened/dialog-after-opened.example.component.html",
Expand Down Expand Up @@ -774,6 +776,8 @@ export const DEMO_PATHS = [
"overlay/overlay-custom-styles/overlay-custom-styles.example.component.ts",
"overlay/overlay-docs/overlay-docs.example.component.html",
"overlay/overlay-docs/overlay-docs.example.component.ts",
"overlay/overlay-focus-trap/overlay-focus-trap.example.component.html",
"overlay/overlay-focus-trap/overlay-focus-trap.example.component.ts",
"overlay/overlay-popup-styles/overlay-popup-styles.example.component.html",
"overlay/overlay-popup-styles/overlay-popup-styles.example.component.ts",
"overlay/overlay-show-hide-toggle/overlay-show-hide-toggle.example.component.html",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<ng-template #dialogTemplate let-close="close">
<nui-dialog-header
title="Delete report"
(closed)="close()"></nui-dialog-header>
<div class="dialog-body">
<p id="nui-dialog-a11y-description" i18n>
The report will be permanently deleted. This action cannot be
undone.
</p>
</div>
<nui-dialog-footer>
<button nui-button type="button" (click)="close()" i18n>
Cancel
</button>
<button
nui-button
type="button"
displayStyle="destructive"
(click)="close()"
i18n>
Delete
</button>
</nui-dialog-footer>
</ng-template>

<button nui-button type="button" (click)="open()" i18n> Delete report </button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import { Component, inject, TemplateRef, ViewChild } from "@angular/core";

import { DialogService } from "@nova-ui/bits";

@Component({
selector: "nui-dialog-accessibility-example",
templateUrl: "./dialog-accessibility.example.component.html",
standalone: false,
})
export class DialogAccessibilityExampleComponent {
@ViewChild("dialogTemplate") public dialogTemplate: TemplateRef<string>;

private readonly dialogService = inject(DialogService);

public open(): void {
this.dialogService.open(this.dialogTemplate, {
ariaDescribedby: "nui-dialog-a11y-description",
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,23 @@ <h2>Keyboard Options</h2>
<nui-dialog-with-keyboard-example></nui-dialog-with-keyboard-example>
</nui-example-wrapper>

<h2>Accessibility</h2>
<p>
Every dialog needs an accessible name. When the dialog contains
<code>nui-dialog-header</code>, it is labelled by the header title
automatically. If you use a custom title element, pass its id as
<code>ariaLabelledby</code>; if there is no visible title, pass
<code>ariaLabel</code> in the <code>options</code> of
<code>DialogService.open</code>. Use <code>ariaDescribedby</code> to point
to the element with the main message, so screen readers announce it when the
dialog opens.
</p>
<nui-example-wrapper
filenamePrefix="dialog-accessibility"
exampleTitle="Dialog Accessibility">
<nui-dialog-accessibility-example></nui-dialog-accessibility-example>
</nui-example-wrapper>

<h2>Static Backdrop</h2>
<p>
By default, nui-dialog closes if user clicks outside the dialog. It is handy
Expand Down
11 changes: 11 additions & 0 deletions packages/bits/demo/src/components/demo/dialog/dialog.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ConfirmationDialogExampleComponent,
DialogActionBeforeClosureExampleComponent,
DialogAfterOpenedExampleComponent,
DialogAccessibilityExampleComponent,
DialogContentExampleComponent,
DialogCustomClassExampleComponent,
DialogDocsComponent,
Expand Down Expand Up @@ -105,6 +106,15 @@ const routes = [
},
},
},
{
path: "accessibility",
component: DialogAccessibilityExampleComponent,
data: {
srlc: {
hideIndicator: true,
},
},
},
{
path: "dialog-visual-test",
component: DialogVisualTestComponent,
Expand Down Expand Up @@ -145,6 +155,7 @@ const routes = [
declarations: [
DialogContentExampleComponent,
ComponentAsContentExampleComponent,
DialogAccessibilityExampleComponent,
ConfirmationDialogExampleComponent,
DialogCustomClassExampleComponent,
DialogDocsComponent,
Expand Down
1 change: 1 addition & 0 deletions packages/bits/demo/src/components/demo/dialog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export * from "./dialog-inside-overlay/dialog-inside-overlay.example.component";
export * from "./dialog-actions-before-closure/dialog-actions-before-closure.example.component";
export * from "./dialog-zindex-test/dialog-zindex-test.component";
export * from "./dialog-after-opened/dialog-after-opened.example.component";
export * from "./dialog-accessibility/dialog-accessibility.example.component";
export * from "./dialog-actions-before-closure/dialog-actions-before-closure.example.component";
export * from "./dialog-test/dialog-test.example.component";
export * from "./dialog-inside-overlay-with-date-time-picker/dialog-inside-overlay-with-date-time-picker.example.component";
1 change: 1 addition & 0 deletions packages/bits/demo/src/components/demo/overlay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export * from "./overlay-test/overlay-test.example.component";
export * from "./overlay-viewport-margin/overlay-viewport-margin-example.component";
export * from "./overlay-arrow/overlay-arrow.example.component";
export * from "./overlay-popup-styles/overlay-popup-styles.example.component";
export * from "./overlay-focus-trap/overlay-focus-trap.example.component";
export * from "./overlay-custom-dialog/overlay-custom-dialog.component";
export * from "./overlay-custom-confirmation-inside-dialog/overlay-custom-confirmation-inside-dialog.component";
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,23 @@
</div>
<!-- By setting the [toggleReference] below we choose the container to apply our overlay to. The chosen container
can later be accessed via the overlay's 'overlayRef' -->
<nui-overlay #overlay [toggleReference]="dialog">
<nui-overlay #overlay [toggleReference]="dialog" [trapFocus]="true">
<div class="overlay-class">
<div class="overlay-content">
<div class="header">Are you sure you want to quit?</div>
<div class="footer">
<button
nui-button
displayStyle="action"
type="button"
i18n
(click)="overlay.hide()">
No
</button>
<button
nui-button
displayStyle="primary"
type="button"
i18n
(click)="overlay.hide(); close()">
Yes
</button>
Expand Down Expand Up @@ -162,7 +164,10 @@
</nui-dialog-footer>
</div>

<nui-overlay #overlayLarge [toggleReference]="dialogLarge">
<nui-overlay
#overlayLarge
[toggleReference]="dialogLarge"
[trapFocus]="true">
<div class="overlay-class">
<div class="overlay-content">
<div class="header">
Expand All @@ -180,14 +185,16 @@
<div class="footer">
<button
nui-button
displayStyle="action"
type="button"
i18n
(click)="overlayLarge.hide()">
Cancel
</button>
<button
nui-button
displayStyle="primary"
type="button"
i18n
(click)="overlayLarge.hide(); close()">
Yes
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
flex-direction: row;
justify-content: center;

button[displayStyle="action"] {
margin-right: @nui-space-md;
button[displayStyle="primary"] {
margin-left: @nui-space-md;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,30 @@ <h2>Toggle Types</h2>

<!-- ------------------------------------------------------------------------------------------- -->

<h2>Accessibility and Focus Trapping</h2>
<p>
When the overlay behaves like a small dialog (it contains form fields or
actions), set <code>trapFocus</code> to <code>true</code>. While the overlay
is open, keyboard focus stays inside it, and when it closes, focus returns
to the element that was focused before. It is disabled by default, so
dropdown-like usages (select, combobox) keep their behavior.
</p>
<p>
Use <code>roleAttr</code>, <code>ariaLabel</code> or
<code>ariaLabelledby</code>, <code>ariaDescribedby</code> and
<code>ariaModal</code> to describe the overlay container to assistive
technologies. The container gets a unique id automatically; use
<code>idAttr</code> to set your own, e.g. to reference it from
<code>aria-controls</code> of the toggle.
</p>
<nui-example-wrapper
filenamePrefix="overlay-focus-trap.example"
exampleTitle="Accessible Overlay with Focus Trap">
<nui-overlay-focus-trap-example></nui-overlay-focus-trap-example>
</nui-example-wrapper>

<!-- ------------------------------------------------------------------------------------------- -->

<h2>Custom Styles</h2>
<p>
In case tweaking the <code>nui-overlay</code> overlay styles via the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<button nui-button #toggle type="button" (click)="overlay.toggle()" i18n>
Edit name
</button>

<nui-overlay
#overlay
[toggleReference]="toggle"
[overlayConfig]="{ panelClass: OVERLAY_WITH_POPUP_STYLES_CLASS }"
roleAttr="dialog"
ariaLabelledby="nui-overlay-focus-trap-title"
[ariaModal]="true"
[trapFocus]="true">
<h4 id="nui-overlay-focus-trap-title" i18n>Edit name</h4>
<nui-textbox
id="nui-overlay-focus-trap-name"
ariaLabel="Name"
i18n-ariaLabel
[(ngModel)]="name"></nui-textbox>
<button nui-button type="button" (click)="overlay.hide()" i18n>
Cancel
</button>
<button
nui-button
type="button"
displayStyle="primary"
(click)="saveName()"
i18n>
Save
</button>
</nui-overlay>

@if (savedName) {
<p i18n>Saved name: {{ savedName }}</p>
}
Loading
Loading