Relationships and grid indices
Relationship attributes connect one element to another by id: "this input is described by that hint". They do not move DOM nodes. The exception is
aria-owns, which changes the accessibility tree's parent. Get an id wrong and the relationship is silently missing, with no error.
In one minute
- A relationship is an id pointer.
aria-describedby="hint"means "the element withid="hint"describes me". - A typo breaks it silently. Check every id by hand or with a script.
aria-activedescendantlets a widget keep focus on itself while pointing at the active child.- For tables and grids that only render some rows, use
aria-rowcount,aria-rowindex,aria-setsizeandaria-posinsetso "row 40 of 1000" stays true.
Imagine sticky notes with names on them. A relationship attribute says "read the note called hint when you get to this box". If the note is called hnt, nobody says anything. The page still looks perfect. Only the person who relies on the note is left without it.
1. Active descendant: focus stays, the pointer moves
In a listbox, real focus can stay on the container while aria-activedescendant holds the id of the option that is "active". Arrow keys
change the attribute. The options are not tab stops. This is how a combobox keeps focus in the text field while the user moves through suggestions.
Step 1. Click the list or Tab to it, then press ↓ and ↑. Compare both versions.
The target must be a DOM descendant of the focused element (or be listed in its aria-owns). Clear the attribute when the popup closes. Keep the
highlighted item visible on screen too: scroll it into view as the user moves (WCAG 2.4.7 Focus Visible).
2. Describing and error messages: ids must match
aria-describedby points to a hint. aria-errormessage points to the failure text, and is only used when aria-invalid="true".
The error text should be visible. Use the lab to see what a typo does.
Step 2. Pick a version, then press "Validate" with the field empty.
✕ Password is too short.
Always give the error a text marker (here "✕") and not just red. Related: aria-details points to longer content and is covered in lesson 16.
Ids must be unique on the page. Duplicate ids mean the pointer lands on the first match, usually the wrong one.
3. Position in a set that is not all there
A long list is often virtualized: only a few rows exist in the page and the rest are created as you scroll. Without help, the screen reader says "1 of 5" for the first
visible row, which is wrong. aria-setsize gives the true total. aria-posinset gives this item's real place, starting at 1.
Step 3. Drag the scroll position. Only 5 rows are in the DOM. Toggle the attributes and read the first row.
If every item of the set is in the DOM, leave both off. The browser counts the children correctly by itself.
4. Grid indices: row 41 of 1000
The same idea for a grid. aria-rowcount and aria-colcount go on the grid. aria-rowindex goes on each row and
aria-colindex on each cell. All start at 1. Use -1 for a total you do not know.
Step 4. This grid is 1000 rows by 12 columns, but only 3 by 3 cells are in the page. Click a cell.
A plain HTML table with every cell in the DOM needs none of this. Also available: aria-rowindextext and aria-colindextext for human labels such as "Q3",
and aria-rowspan and aria-colspan matching HTML rowspan and colspan (set on the spanning cell only).
5. Quick check: which attribute?
6. Reference: every relationship attribute
| aria-activedescendant | Id of the focused descendant while DOM focus remains on the composite. The target must be a DOM descendant, or an aria-owns descendant. Clear it when the popup closes. |
|---|---|
| aria-controls | Ids of elements this control affects, such as a tab’s panel or a disclosure’s region. It does not show or hide them. Your script does. It tells the user the relationship exists. |
| aria-owns | Ids of elements that should be treated as children in the accessibility tree even though they are not DOM children. Use it when a portal renders the popup at the end of body. Do not use it on a role whose children are presentational. Do not use it to fake a parent you could have written in HTML. |
| aria-flowto | Ids of the next recommended reading targets, an alternative to source order. Rare. If you need it, the visual order and the DOM order have already diverged too far. Fix the order first. |
| aria-errormessage | Id of the error for an aria-invalid field. The message element should be visible. Describedby can also point at the error. Errormessage is the specific relationship for the failure text. |
| aria-details | Covered in lesson 16. It is a relationship to longer content, not a name and not a live region. |
| aria-posinset | This item’s position in a set, starting at 1, when the full set is not in the DOM. A virtualized tree uses it so “3 of 200” is still true while only 20 rows are mounted. |
| aria-setsize | The size of that set. Set it together with posinset. If the whole set is in the DOM, omit both and let the tree count the children. |
| aria-level | Also a relationship to structure: heading level or tree depth when nesting is flattened for virtualization. |
It rewrites the accessibility tree's parent and child links, which can reorder what users hear. If you can write the child inside its parent in HTML, do that. aria-flowto is a sign that visual and DOM order have drifted apart. Fix the order first.
7. Reference: grid and table indices
Use these when the grid is virtualized or spanned, so the accessibility tree can still report "row 40 of 1000, column 3".
| aria-rowcount | Total rows, including rows not in the DOM. Use −1 if the total is unknown. |
|---|---|
| aria-colcount | Total columns, the same way. |
| aria-rowindex | This row’s index, starting at 1, in the full table. |
| aria-colindex | This cell’s column index, starting at 1. |
| aria-rowindextext | A human string instead of the row number, when the number is not what should be spoken. |
| aria-colindextext | A human string instead of the column number. |
| aria-rowspan | How many rows this cell spans. Matches the HTML rowspan attribute. Set it on the cell that spans, not on the cells it covers. |
| aria-colspan | How many columns this cell spans. Same idea as HTML colspan. |
8. Drag and drop attributes are deprecated
Do not add these to new work. Give every drag a keyboard path with buttons such as "Move up" and "Move down" (WCAG 2.5.7 Dragging Movements).
| aria-grabbed | Was true, false, or undefined for a draggable object. Deprecated. Do not add it to new work. A drag operation still needs a keyboard path: buttons that pick up and drop, not only a pointer gesture. |
|---|---|
| aria-dropeffect | Was a token list of what a drop would do: copy, move, link, execute, popup, none. Deprecated. The drop target should be a real control with a name that says what will happen. |
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
| Typo in an id reference | Silently ignored. | Check getElementById for every reference. |
| Duplicate ids | Pointer hits the wrong element. | Make ids unique. |
Arrow keys highlight but aria-activedescendant never changes | Screen reader keeps reading the first item. | Update the attribute on every move. |
Virtualized list with no aria-setsize | "1 of 5" instead of "43 of 200". | Set size and position. |
aria-owns to fake a parent | Scrambles reading order. | Write the real HTML parent. |
New work with aria-grabbed | Deprecated. | Buttons with names. |
What you have now
Lessons 15 through 21 cover the WAI-ARIA 1.3 surface used in real pages: the model, every naming attribute, every widget state, standalone and composite roles,
structure and landmarks, live regions, window roles, ariaNotify, relationships, and grid indices. Abstract roles stay off the page. Native HTML stays first.
When a state changes, the attribute changes with it.
- In lab 1, switch to "Attribute updated". In the browser's accessibility panel, watch
aria-activedescendantchange as you press the arrows. - In lab 2, rename one id in dev tools and see the reference check turn red.
Remember
- Relationships are id pointers. A wrong id fails silently.
aria-activedescendantmust change with the visual highlight.- Virtualized sets and grids need size, position, and index attributes.
aria-ownsandaria-flowtoare last resorts. Drag-and-drop attributes are deprecated.