Skip to content

Commit 7cf7363

Browse files
nikolay-kolarovflovogt
authored andcommitted
feat(ui5): Add accessibility best practices skill (#85)
Adds a new skill for UI5 accessibility best practices covering keyboard navigation, labelling, landmarks, heading levels, reading order, target size, shortcuts, and invisible messages. Includes reference documentation and test fixtures for each topic. JIRA: FIORITECHP1-35629
1 parent bc11228 commit 7cf7363

26 files changed

Lines changed: 1715 additions & 0 deletions

plugins/ui5/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ Development guidelines and coding standards derived from official SAP UI5 guidel
2828

2929
**Note**: For TypeScript conversion specifically, use the separate [`ui5-typescript-conversion`](https://github.com/UI5/plugins-coding-agents/tree/main/plugins/ui5-typescript-conversion) plugin.
3030

31+
#### ui5-best-practices-accessibility
32+
33+
Accessibility guidelines and review checklist for UI5 views, fragments, and controllers:
34+
35+
- **Landmarks** - `landmarkInfo` and `accessibleRole` for `DynamicPage`, `Page`, `Panel`, `ObjectPage`, `FlexibleColumnLayout`
36+
- **Labeling** - `<Label labelFor>` for inputs, `ariaLabelledBy` for tables, tooltips for icon-only buttons, `alt` for standalone icons and images
37+
- **Heading levels** - Explicit `level` on `<Title>`, no heading level jumps within a view
38+
- **Focus handling** - `initialFocus` on `Dialog`/`Popover`, fast navigation groups, no `tabindex > 0`
39+
- **Keyboard shortcuts** - `CommandExecution` for action buttons that should support keyboard shortcuts
40+
- **Invisible messaging** - `InvisibleMessage.announce()` for dynamic state changes visible to sighted users only
41+
- **Reading order** - Controls not visually reordered out of DOM sequence; `ariaDescribedBy` pointing to correct DOM order
42+
- **Target size** - `reactiveAreaMode` for interactive controls in dense layouts
43+
3144
#### ui5-best-practices-integration-cards
3245

3346
Development guidelines for UI Integration Cards (also known as UI5 Integration Cards):
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
name: ui5-best-practices-accessibility
3+
description: |
4+
This skill should be used when the user asks to audit, fix, check, review, or
5+
improve accessibility, a11y, ARIA, WCAG compliance, landmarks, labeling, heading
6+
levels, focus handling, keyboard navigation, keyboard shortcuts, screen reader
7+
support, invisible messaging, reading order, or touch / target size in UI5
8+
application files (views, fragments, controllers). Also use when the user
9+
creates a new UI5 view, fragment, or controller and wants it to be accessible,
10+
or asks whether a specific control (Dialog, Table, Panel, etc.) meets
11+
accessibility requirements.
12+
13+
Keywords: accessibility, a11y, ARIA, WCAG, screen reader, NVDA, JAWS, VoiceOver,
14+
landmark, landmarkInfo, accessibleRole, ariaLabelledBy, ariaDescribedBy,
15+
labelFor, tooltip, alt text, decorative, heading level, initialFocus, F6,
16+
fast nav, fastnavgroup, tabindex, CommandExecution, keyboard shortcut,
17+
InvisibleMessage, announce, reading order, reactiveAreaMode, target size.
18+
---
19+
20+
# Accessibility Review
21+
22+
Accessibility in UI5 is incorporated in two levels: framework and application.
23+
This review supports what application developers must still provide explicitly
24+
to improve the accessibility of their application.
25+
26+
## Step 1 — Find the files
27+
28+
If `$ARGUMENTS` lists specific files, review only those.
29+
30+
Otherwise, discover all app source files automatically:
31+
32+
```!
33+
find . \( -name "*.view.xml" -o -name "*.fragment.xml" -o -name "*.controller.js" \) \
34+
-not -path "*/node_modules/*" \
35+
-not -path "*/dist/*" \
36+
-not -path "*/test/*" \
37+
-not -path "*/resources/*" \
38+
| sort
39+
```
40+
41+
If more than 15 files are found, use `AskUserQuestion` to let the user choose:
42+
- Review everything (may take a moment)
43+
- Focus on a specific folder or area
44+
45+
Read each file in scope.
46+
47+
## Step 2 — Review
48+
49+
Check the code against the eight topics below. For each topic where you find a gap,
50+
**read the corresponding topic file before writing the fix** — it contains the correct
51+
API pattern and wrong/correct examples.
52+
53+
| # | Topic | What to detect | Topic file |
54+
|---|-------|---------------|------------|
55+
| 1 | Landmarks | `DynamicPage`, `Page`, `Panel`, `ObjectPage`, `FlexibleColumnLayout` missing `landmarkInfo` or `accessibleRole`; landmark role set without its corresponding label (e.g. `rootRole` without `rootLabel`) | `references/landmark.md` |
56+
| 2 | Labeling | Inputs without `<Label labelFor>` (except inside `SimpleForm`); Tables without `ariaLabelledBy`; icon-only `Button` without `tooltip`; standalone `Icon` without `alt` and not marked `decorative`; `Image` with `decorative=false` and no `alt`; `Dialog` with `showHeader:false` and no `ariaLabelledBy` | `references/labeling.md` |
57+
| 3 | Heading levels | `<Title>` without explicit `level`; heading level jumps (e.g. H1 → H3) within a view | `references/heading.md` |
58+
| 4 | Focus & keyboard | `Dialog` or `Popover` without `initialFocus` when a specific starting element is required; larger composite areas that act as distinct logical regions and need a `sap-ui-fastnavgroup` `CustomData` entry; `tabindex` values greater than 0 in rendered HTML | `references/keyboard.md` |
59+
| 5 | Keyboard shortcuts | Action buttons (save, delete, etc.) using plain `press=".onX"` that should support keyboard shortcuts but have no `CommandExecution` | `references/shortcut.md` |
60+
| 6 | Invisible messaging | Dynamic state changes (save confirmations, errors, filter results) that are visible-only with no `InvisibleMessage.announce()` call in the handler | `references/invisible-message.md` |
61+
| 7 | Reading order | Controls visually reordered via CSS/layout but out of sequence in XML; `ariaDescribedBy` pointing to IDs that appear later in the DOM | `references/reading-order.md` |
62+
| 8 | Target size | `Link`, `ObjectIdentifier`, `ObjectStatus`, `ObjectNumber`, `ObjectMarker`, `ObjectAttribute` inside an interactive container without `reactiveAreaMode`; or in other dense layout without spacing | `references/target-size.md` |
63+
64+
## Step 3 — Report
65+
66+
For each gap found:
67+
68+
**Issue**: one line — names the control and the missing property/association
69+
**Impact**: `critical` (blocks AT users entirely) | `serious` (significant barrier) | `moderate` (partial barrier) | `minor` (best practice, low direct impact)
70+
**Why**: one sentence on user impact
71+
**Fix**: minimal corrected XML or JS snippet (only the changed part)
72+
73+
Group findings by topic, critical/serious first within each group. End with a summary count by impact level.
74+
If no gaps are found, say so.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Heading Levels
2+
3+
Heading hierarchy lets screen reader users scan the page structure and jump between
4+
sections. Missing or skipped heading levels break this navigation.
5+
6+
**Rule: every `<Title>` used as a heading must have an explicit `level` property set.**
7+
8+
**Wrong:**
9+
```xml
10+
<Title text="{i18n>orderDetailsTitle}"/>
11+
```
12+
13+
**Correct:**
14+
```xml
15+
<Title level="H1" text="{i18n>orderDetailsTitle}"/> <!-- page title -->
16+
<Title level="H2" text="{i18n>lineItemsTitle}"/> <!-- section header -->
17+
<Title level="H3" text="{i18n>itemNotesTitle}"/> <!-- subsection -->
18+
```
19+
20+
## Heading hierarchy rules
21+
22+
- Each page should have exactly one `H1` — the page title.
23+
- Section headers = `H2`, subsection headers = `H3`, and so on.
24+
- **Never skip levels** — H1 → H3 with no H2 breaks the document outline and
25+
confuses AT users navigating by heading.
26+
27+
## `sap.m.Dialog` heading level
28+
29+
When using the `title` property, the framework renders it as `<h1>` automatically — no extra configuration needed. When using `customHeader` or `showHeader: false`, set `level: TitleLevel.H1` explicitly on the `Title` control.
30+
31+
**Wrong:**
32+
```xml
33+
<Dialog>
34+
<customHeader>
35+
<Toolbar>
36+
<Title id="dlgTitle" text="{i18n>confirmDeletionTitle}"/>
37+
</Toolbar>
38+
</customHeader>
39+
</Dialog>
40+
```
41+
42+
**Correct:**
43+
```xml
44+
<Dialog ariaLabelledBy="dlgTitle">
45+
<customHeader>
46+
<Toolbar>
47+
<Title id="dlgTitle" text="{i18n>confirmDeletionTitle}" level="H1"/>
48+
</Toolbar>
49+
</customHeader>
50+
</Dialog>
51+
```
52+
53+
## `sap.m.Panel` heading level
54+
55+
A `Panel` with `headerText` renders that text as a heading. If the heading level needs
56+
to be explicitly controlled, replace `headerText` with a `headerToolbar` aggregation
57+
containing a `Title` with an explicit `level`:
58+
59+
```xml
60+
<Panel accessibleRole="Region">
61+
<headerToolbar>
62+
<Toolbar>
63+
<Title level="H3" text="{i18n>shippingDetailsTitle}"/>
64+
</Toolbar>
65+
</headerToolbar>
66+
...
67+
</Panel>
68+
```
69+
70+
## Available values
71+
72+
`H1` · `H2` · `H3` · `H4` · `H5` · `H6` · `Auto` (avoid — use explicit levels)
73+
74+
## i18n bindings
75+
76+
All snippets above use `{i18n>...}` for user-facing strings — bind every UI text to
77+
the resource model, never hard-code English literals in the view.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Invisible Messaging
2+
3+
Use `sap.ui.core.InvisibleMessage` to announce dynamic state changes to screen reader
4+
users who would otherwise miss purely visual updates (badge counts, success banners,
5+
loading indicators, filter results).
6+
7+
All announcement text and static labels below are pulled from the i18n resource
8+
bundle — bind every UI text to the resource model, never hard-code English literals.
9+
10+
## When not to use
11+
12+
- Do not provide information exclusively for AT users — screen reader users should not receive content that sighted users cannot access
13+
- Do not use it to hide long texts — if the information matters, show it
14+
15+
## Dynamic announcements — `InvisibleMessage`
16+
17+
```js
18+
sap.ui.define([
19+
"sap/ui/core/mvc/Controller",
20+
"sap/ui/core/InvisibleMessage",
21+
"sap/ui/core/library"
22+
], function(Controller, InvisibleMessage, library) {
23+
"use strict";
24+
25+
var InvisibleMessageMode = library.InvisibleMessageMode;
26+
27+
return Controller.extend("my.app.Controller", {
28+
onInit: function () {
29+
this.oIM = InvisibleMessage.getInstance();
30+
this.oResourceBundle = this.getOwnerComponent()
31+
.getModel("i18n")
32+
.getResourceBundle();
33+
},
34+
35+
onDeleteItems: function (iCount) {
36+
// ... perform deletion ...
37+
this.oIM.announce(
38+
this.oResourceBundle.getText("itemsDeletedAnnouncement", [iCount]),
39+
InvisibleMessageMode.Polite
40+
);
41+
},
42+
43+
onSubmitError: function () {
44+
// ... handle error ...
45+
this.oIM.announce(
46+
this.oResourceBundle.getText("submissionFailedAnnouncement"),
47+
InvisibleMessageMode.Assertive
48+
);
49+
}
50+
});
51+
});
52+
```
53+
54+
| Mode | Behavior | When to use |
55+
|---|---|---|
56+
| `Polite` | Waits for a pause in current speech | Status updates, counts, non-urgent feedback |
57+
| `Assertive` | Interrupts current speech immediately | Errors, warnings, critical state changes |
58+
59+
## Static ARIA references — `core:InvisibleText`
60+
61+
Use when you need a hidden text node that other controls reference via `ariaLabelledBy`
62+
or `ariaDescribedBy`, and a visible `<Label>` is not suitable:
63+
64+
```xml
65+
<core:InvisibleText id="postalLabel" text="{i18n>postalCodeLabel}"/>
66+
<core:InvisibleText id="cityLabel" text="{i18n>cityLabel}"/>
67+
<Input ariaLabelledBy="postalLabel" value="{addr>/postalCode}" fieldWidth="35%"/>
68+
<Input ariaLabelledBy="cityLabel" value="{addr>/city}" fieldWidth="35%"/>
69+
```
70+
71+
Place `InvisibleText` nodes **before** the controls that reference them.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Focus Handling and Keyboard Navigation
2+
3+
## Initial focus — `initialFocus`
4+
5+
When a Dialog or Popover opens, set which element receives focus. Without this, focus lands
6+
on the first focusable element, which may not be the right starting point for the task.
7+
8+
```xml
9+
<Popover title="{i18n>productDetailsTitle}" initialFocus="firstActionBtn">
10+
<content>
11+
<VBox>
12+
<Text text="{i18n>notebookProductName}"/>
13+
<Button id="firstActionBtn" text="{i18n>addToCartButton}"/>
14+
</VBox>
15+
</content>
16+
</Popover>
17+
```
18+
19+
Same attribute on `<Dialog initialFocus="elementId">`.
20+
21+
All user-facing strings above are bound via `{i18n>...}` — bind every UI text to the
22+
resource model, never hard-code English literals in the view.
23+
24+
## F6 fast navigation
25+
26+
F6 / Shift+F6 lets users jump between logical groups. Some standard containers create
27+
F6 groups automatically — for example, `sap.m.Panel` (the whole panel is one group)
28+
and `sap.uxap.ObjectPageSection` (each section is a separate group).
29+
30+
**Adding or removing a custom area from the F6 chain**
31+
32+
Standard containers create their own F6 groups automatically. Groups can also be nested —
33+
pressing F6 inside a nested group moves focus to the next group at that level; if none
34+
exists, focus moves up to the parent group.
35+
36+
To add a custom area as an F6 group, use the `sap-ui-fastnavgroup` key via `CustomData`:
37+
38+
```xml
39+
<!-- XML view -->
40+
<VBox>
41+
<customData>
42+
<core:CustomData key="sap-ui-fastnavgroup" value="true" writeToDom="true"/>
43+
</customData>
44+
</VBox>
45+
```
46+
47+
```js
48+
// Controller / JS
49+
oControl.data("sap-ui-fastnavgroup", "true", true /* writeToDom */);
50+
```
51+
52+
To remove a group that a control defines by default, set the value to `"false"`:
53+
54+
```js
55+
oControl.data("sap-ui-fastnavgroup", "false", true /* writeToDom */);
56+
```
57+
58+
## Custom interactive elements — `tabindex`
59+
60+
Native HTML elements (button, input, a) are focusable by default. Custom elements
61+
rendered with a non-interactive tag need explicit `tabindex`:
62+
63+
```html
64+
<!-- Focusable custom widget -->
65+
<div role="combobox" tabindex="0" ...> ... </div>
66+
67+
<!-- Remove from tab order but keep programmatically focusable -->
68+
<div tabindex="-1" ...> ... </div>
69+
```
70+
71+
Avoid `tabindex` values greater than 0 — they override the natural reading order.

0 commit comments

Comments
 (0)