Skip to content

Commit ad435fa

Browse files
feat(ui5): Add accessibility best practices skill
1 parent d1e3a43 commit ad435fa

24 files changed

Lines changed: 1235 additions & 0 deletions

plugins/ui5/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ Authoritative development guidelines for all UI5 table controls (SAPUI5 1.136+ L
6464
- **Personalization** - `sap.m.p13n.Engine` integration
6565
- **Cell templates & alignment** - Type-based alignment and model type namespace rules
6666

67+
#### ui5-best-practices-accessibility
68+
69+
Accessibility guidelines and review checklist for UI5 views, fragments, and controllers:
70+
71+
- **Landmarks** - `landmarkInfo` and `accessibleRole` for `DynamicPage`, `Page`, `Panel`, `ObjectPage`, `FlexibleColumnLayout`
72+
- **Labeling** - `<Label labelFor>` for inputs, `ariaLabelledBy` for tables, tooltips for icon-only buttons, `alt` for standalone icons and images
73+
- **Heading levels** - Explicit `level` on `<Title>`, no heading level jumps within a view
74+
- **Focus handling** - `initialFocus` on `Dialog`/`Popover`, fast navigation groups, no `tabindex > 0`
75+
- **Keyboard shortcuts** - `CommandExecution` for action buttons that should support keyboard shortcuts
76+
- **Invisible messaging** - `InvisibleMessage.announce()` for dynamic state changes visible to sighted users only
77+
- **Reading order** - Controls not visually reordered out of DOM sequence; `ariaDescribedBy` pointing to correct DOM order
78+
- **Target size** - `reactiveAreaMode` for interactive controls in dense layouts
79+
6780
---
6881

6982
## Installation
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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, landmarks, labeling, heading levels,
6+
keyboard shortcuts, screen reader support, invisible messaging, reading order,
7+
or touch target size in UI5 application files (views, fragments, controllers).
8+
Also use when the user creates a new UI5 view, fragment, or controller and
9+
wants it to be accessible, or asks whether a specific control (Dialog, Table,
10+
Panel, etc.) meets accessibility requirements.
11+
allowed-tools: Read Bash AskUserQuestion
12+
---
13+
14+
# Accessibility Review
15+
16+
Accessibility in UI5 is incorporated in two levels: framework and application.
17+
This review supports what application developers must still provide explicitly
18+
to improve the accessibility of their application.
19+
20+
## Step 1 — Find the files
21+
22+
If `$ARGUMENTS` lists specific files, review only those.
23+
24+
Otherwise, discover all app source files automatically:
25+
26+
```!
27+
find . \( -name "*.view.xml" -o -name "*.fragment.xml" -o -name "*.controller.js" \) \
28+
-not -path "*/node_modules/*" \
29+
-not -path "*/dist/*" \
30+
-not -path "*/test/*" \
31+
-not -path "*/resources/*" \
32+
| sort
33+
```
34+
35+
If more than 15 files are found, use `AskUserQuestion` to let the user choose:
36+
- Review everything (may take a moment)
37+
- Focus on a specific folder or area
38+
39+
Read each file in scope.
40+
41+
## Step 2 — Review
42+
43+
Check the code against the eight topics below. For each topic where you find a gap,
44+
**read the corresponding topic file before writing the fix** — it contains the correct
45+
API pattern and wrong/correct examples.
46+
47+
| # | Topic | What to detect | Topic file |
48+
|---|-------|---------------|------------|
49+
| 1 | Landmarks | `DynamicPage`, `Page`, `Panel`, `ObjectPage`, `FlexibleColumnLayout` missing `landmarkInfo` or `accessibleRole`; landmark role set without its corresponding label (e.g. `rootRole` without `rootLabel`) | `${CLAUDE_SKILL_DIR}/references/landmark.md` |
50+
| 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` | `${CLAUDE_SKILL_DIR}/references/labeling.md` |
51+
| 3 | Heading levels | `<Title>` without explicit `level`; heading level jumps (e.g. H1 → H3) within a view | `${CLAUDE_SKILL_DIR}/references/heading.md` |
52+
| 4 | Focus handling | `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 | `${CLAUDE_SKILL_DIR}/references/keyboard.md` |
53+
| 5 | Keyboard shortcuts | Action buttons (save, delete, etc.) using plain `press=".onX"` that should support keyboard shortcuts but have no `CommandExecution` | `${CLAUDE_SKILL_DIR}/references/shortcut.md` |
54+
| 6 | Invisible messaging | Dynamic state changes (save confirmations, errors, filter results) that are visible-only with no `InvisibleMessage.announce()` call in the handler | `${CLAUDE_SKILL_DIR}/references/invisible-message.md` |
55+
| 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 | `${CLAUDE_SKILL_DIR}/references/reading-order.md` |
56+
| 8 | Target size | `Link`, `ObjectIdentifier`, `ObjectStatus`, `ObjectNumber`, `ObjectMarker`, `ObjectAttribute` inside an interactive container without `reactiveAreaMode`; or in other dense layout without spacing | `${CLAUDE_SKILL_DIR}/references/target-size.md` |
57+
58+
## Step 3 — Report
59+
60+
For each gap found:
61+
62+
**Issue**: one line — names the control and the missing property/association
63+
**Impact**: `critical` (blocks AT users entirely) | `serious` (significant barrier) | `moderate` (partial barrier) | `minor` (best practice, low direct impact)
64+
**Why**: one sentence on user impact
65+
**Fix**: minimal corrected XML or JS snippet (only the changed part)
66+
67+
Group findings by topic, critical/serious first within each group. End with a summary count by impact level.
68+
If no gaps are found, say so.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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="Order Details"/>
11+
```
12+
13+
**Correct:**
14+
```xml
15+
<Title level="H1" text="Order Details"/> <!-- page title -->
16+
<Title level="H2" text="Line Items"/> <!-- section header -->
17+
<Title level="H3" text="Item Notes"/> <!-- 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="Confirm Deletion"/>
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="Confirm Deletion" 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="Shipping Details"/>
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)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
## When not to use
8+
9+
- Do not provide information exclusively for AT users — screen reader users should not receive content that sighted users cannot access
10+
- Do not use it to hide long texts — if the information matters, show it visibly
11+
12+
## Dynamic announcements — `InvisibleMessage`
13+
14+
```js
15+
sap.ui.define([
16+
"sap/ui/core/mvc/Controller",
17+
"sap/ui/core/InvisibleMessage",
18+
"sap/ui/core/library"
19+
], function(Controller, InvisibleMessage, library) {
20+
"use strict";
21+
22+
var InvisibleMessageMode = library.InvisibleMessageMode;
23+
24+
return Controller.extend("my.app.Controller", {
25+
onInit: function () {
26+
this.oIM = InvisibleMessage.getInstance();
27+
},
28+
29+
onDeleteItems: function () {
30+
// ... perform deletion ...
31+
this.oIM.announce("3 items deleted", InvisibleMessageMode.Polite);
32+
},
33+
34+
onSubmitError: function () {
35+
// ... handle error ...
36+
this.oIM.announce(
37+
"Submission failed. Please check required fields.",
38+
InvisibleMessageMode.Assertive
39+
);
40+
}
41+
});
42+
});
43+
```
44+
45+
| Mode | Behavior | When to use |
46+
|---|---|---|
47+
| `Polite` | Waits for a pause in current speech | Status updates, counts, non-urgent feedback |
48+
| `Assertive` | Interrupts current speech immediately | Errors, warnings, critical state changes |
49+
50+
## Static ARIA references — `core:InvisibleText`
51+
52+
Use when you need a hidden text node that other controls reference via `ariaLabelledBy`
53+
or `ariaDescribedBy`, and a visible `<Label>` is not suitable:
54+
55+
```xml
56+
<core:InvisibleText id="postalLabel" text="Postal code"/>
57+
<core:InvisibleText id="cityLabel" text="City"/>
58+
<Input ariaLabelledBy="postalLabel" value="12345" fieldWidth="35%"/>
59+
<Input ariaLabelledBy="cityLabel" value="Sofia" fieldWidth="35%"/>
60+
```
61+
62+
Place `InvisibleText` nodes **before** the controls that reference them.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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="Product Details" initialFocus="firstActionBtn">
10+
<content>
11+
<VBox>
12+
<Text text="Notebook Basic 15"/>
13+
<Button id="firstActionBtn" text="Add to Cart"/>
14+
</VBox>
15+
</content>
16+
</Popover>
17+
```
18+
19+
Same attribute on `<Dialog initialFocus="elementId">`.
20+
21+
## F6 fast navigation
22+
23+
F6 / Shift+F6 lets users jump between logical groups. Some standard containers create
24+
F6 groups automatically — for example, `sap.m.Panel` (the whole panel is one group)
25+
and `sap.uxap.ObjectPageSection` (each section is a separate group).
26+
27+
**Adding or removing a custom area from the F6 chain**
28+
29+
Standard containers create their own F6 groups automatically. Groups can also be nested —
30+
pressing F6 inside a nested group moves focus to the next group at that level; if none
31+
exists, focus moves up to the parent group.
32+
33+
To add a custom area as an F6 group, use the `sap-ui-fastnavgroup` key via `CustomData`:
34+
35+
```xml
36+
<!-- XML view -->
37+
<VBox>
38+
<customData>
39+
<core:CustomData key="sap-ui-fastnavgroup" value="true" writeToDom="true"/>
40+
</customData>
41+
</VBox>
42+
```
43+
44+
```js
45+
// Controller / JS
46+
oControl.data("sap-ui-fastnavgroup", "true", true /* writeToDom */);
47+
```
48+
49+
To remove a group that a control defines by default, set the value to `"false"`:
50+
51+
```js
52+
oControl.data("sap-ui-fastnavgroup", "false", true /* writeToDom */);
53+
```
54+
55+
## Custom interactive elements — `tabindex`
56+
57+
Native HTML elements (button, input, a) are focusable by default. Custom elements
58+
rendered with a non-interactive tag need explicit `tabindex`:
59+
60+
```html
61+
<!-- Focusable custom widget -->
62+
<div role="combobox" tabindex="0" ...> ... </div>
63+
64+
<!-- Remove from tab order but keep programmatically focusable -->
65+
<div tabindex="-1" ...> ... </div>
66+
```
67+
68+
Avoid `tabindex` values greater than 0 — they override the natural reading order.

0 commit comments

Comments
 (0)