Skip to content

feat(a11y): Add keyboard navigation, focus management, and ARIA labels to pipeline nodes and groups - #322

Merged
nicolethoen merged 1 commit into
patternfly:mainfrom
jeff-phillips-18:a11y-pipelines
Aug 27, 2026
Merged

feat(a11y): Add keyboard navigation, focus management, and ARIA labels to pipeline nodes and groups#322
nicolethoen merged 1 commit into
patternfly:mainfrom
jeff-phillips-18:a11y-pipelines

Conversation

@jeff-phillips-18

Copy link
Copy Markdown
Member

What

Closes #319

Description

Addresses big ticket accessibility issues with pipelines layout.

  • Add keyboard navigation (Enter/Space) and visible focus indicators to pipeline task nodes, task groups, and label action icons so the topology graph can be operated without a mouse
  • Introduce aria-label props on TaskNode, DefaultTaskGroup, and LabelActionIcon, with auto-generated labels that include the node's run status (e.g. "Build (succeeded)")
  • Sort rendered nodes by position (top-to-bottom, left-to-right) so DOM/tab order matches the visual layout of the pipeline
  • Add tabIndex={-1} to layer <g> elements so focus does not get trapped when tabbing through the graph
  • Use :focus-visible outlines on pill backgrounds, status icon circles, and group backgrounds using PatternFly focus ring tokens
  • Add labelTooltipTrigger prop to TaskNode / TaskPill to allow controlling tooltip trigger behavior (e.g. mouseenter vs focus). This is useful to prevent focus on the pill label when trucated and announcing the label a second time.
  • Wire actionIconAriaLabel through NodeLabelLabelActionIcon and add collapseAriaLabel / expandAriaLabel props to DefaultTaskGroup for accessible expand/collapse buttons

Type of change

  • Feature

Screen shots / Gifs for design review

See https://pipelines-a11y.surge.sh/

@jeff-phillips-18

Copy link
Copy Markdown
Member Author

/cc @danreed-rh

@jeff-phillips-18
jeff-phillips-18 force-pushed the a11y-pipelines branch 5 times, most recently from d304dff to 35bcbf9 Compare August 18, 2026 14:09
interface LabelActionIconProps {
className?: string;
icon: React.ReactElement;
'aria-label'?: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are defining aria-label as the prop name here, and adding it to the calling definition, but then immediately re-casting it as 'ariaLabel' below, can we just have it as ariaLabel here as well? Do we expect this component to be called like a normal html element where a user might pass in 'aria-label' as a prop and that's why we do it this way? I see it used a couple of times in this PR, made me curious

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, in the base PatternFly react repo, that's how we handle aria props (use kebab case, but later use camel case). Just for consistency probably better to keep it like this here as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was just following PatternFly conventions.

@danreed-rh

Copy link
Copy Markdown

When going through the demo (and I'm not sure if me experiencing this in the demo means a user would experience it when implemented), I noticed that if I tab through the items and then select an item with space or keyboard, I visually see the item gain focus. Presumably this would open a details window if that was available like in odh-dashboard. When I focus on an item, and then press tab, tab focus returns to the browser tabs at the top of the screen, as if I had reached the end of focusable content on the page. I think each time you select an item it puts it on the bottom of the dom tree, by selecting items by clicking them or tabbing to them and pressing space, I'm able to re-order them in the tree, each item I select gets added to the bottom of the stack.
Everything else looks great! VoiceOver testing on my mac was good too!

@jeff-phillips-18

Copy link
Copy Markdown
Member Author

When going through the demo (and I'm not sure if me experiencing this in the demo means a user would experience it when implemented), I noticed that if I tab through the items and then select an item with space or keyboard, I visually see the item gain focus. Presumably this would open a details window if that was available like in odh-dashboard. When I focus on an item, and then press tab, tab focus returns to the browser tabs at the top of the screen, as if I had reached the end of focusable content on the page. I think each time you select an item it puts it on the bottom of the dom tree, by selecting items by clicking them or tabbing to them and pressing space, I'm able to re-order them in the tree, each item I select gets added to the bottom of the stack. Everything else looks great! VoiceOver testing on my mac was good too!

Thanks @danreed-rh. I've looked into this some more. I found the reason to be due to how withSelect works. It takes an option raiseOnSelect which is true by default. This causes the selection to raise the element, hence putting it at the end of the dom.

I have updated the demo to use withSelect({ raiseOnSelect: false }) for the nodes and groups. It should work better now. Please refresh https://pipelines-a11y.surge.sh/ and give it another try.

I also updated the Accessibility.md file to mention this.

<TaskNode
element={element}
tabIndex={data.tabIndex}
labelTooltipTrigger="mouseenter"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context menus are not able to be opened or focused via keyboard.

If you add an actionIconAriaLabel to these TaskNodes, i think that should give it a tabindex because of the logic on LabelActionIcon.tsx:507

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will, this is for the Set tab indices option. When that is off, no data.tabIndex will be set.

onChange={(_event, checked) => pipelineOptions.setSetTabIndices(checked)}
label="Set tab indices"
/>
</ToolbarItem>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this option is for... when I check it, i cannot keyboard into the pipeline at all.

As far as i can tell, the tab order is correct without needing to check the 'Set tab indices' box...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a demo where we set an ordered tabIndex on each node. Focus does go out the the browser before coming back to the graph. You can see that the tab order is slightly different in that it does not tab to the groups first before tabbing to the nodes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this demo is too confusing. I'm removing it.

@thatblindgeye thatblindgeye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to file comments below we likely want to give nodes and groups an actual semantic role, plus allow providing other attributes as necessary. Right now the nodes are just being announced as "graphics symbol", which doesnt' really tell the user they can interact with the node nor its current state (in terms of selected/pressed, expanded, etc). This maybe could be a followup; deciding what roles to give each and what attributes would need to be added/documented might need a little discussion.


Pipeline task nodes and groups are keyboard accessible by default. `<TaskNode>` and `<DefaultTaskGroup>` set `tabIndex` to `0`, so you can tab to these components.

To keep truncated labels from being announced twice, set `labelTooltipTrigger` to `mouseenter` on `<TaskNode>` or `<DefaultTaskGroup>`. This keeps the label from receiving focus during keyboard navigation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't prevent the tooltips from triggering on keyboard focus. Instead (assuming the PF Tooltip is being used) you could pass aria="none" and aria-live="off" on the Tooltip component to help prevent duplicate announcement.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot get this to work. It is still getting to a "group" and announcing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now can we enable focus trigger the tooltip, and have looking into the repetitive announcement as a followwup? Between both less-than-ideal behaviors, I think exposing the full node name via keyboard might be less egregious than duplicate announcment via SR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's opt-in to use the hover trigger, by default it is focus. We can try to improve the default behavior as a follow up.

The full node name is still announced when the node itself gets focus (along with it's status).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, yeah a followup to potentially tweak this would be good, for keyboard without a screen reader


To keep truncated labels from being announced twice, set `labelTooltipTrigger` to `mouseenter` on `<TaskNode>` or `<DefaultTaskGroup>`. This keeps the label from receiving focus during keyboard navigation.

Tab order follows grouping and the order nodes are added to the model. Groups come first, followed by nodes in the order they were added. To control tab order, set `tabIndex` on `<TaskNode>` or `<DefaultTaskGroup>` when you can determine the presentation order programmatically. Explicit `tabIndex` values also keep selection from changing tab order by moving the selected item to the end.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we maybe note that manually setting tab order will require the consumer to ensure tab order on the entire page doesnt break and that reading/focus order from a page level still makes sense?

Comment on lines +16 to +21
## Known issues

The pipelines layout has the following known accessibility issues:

- Selecting a node reorders it to the last item in its layer, which changes the node's place in the tab order. Further selections move that node after the previously selected node, and so on.
- Setting `tabIndex` on nodes and groups avoids the selection issue. It can also change how focus enters the graph, so focus may move to browser controls.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if wew really need to include this section. We could instead mention the issue of selection causing a reorder when mentioning the manual tab order control above.

If the only way to prevent selection causing a tab reorder on a node is via manual control of that tab order, we may also want to strongly recommend consumers handle that themselves (taking into account that they would also need to ensure general page tab order doesn't break or anything). Otherwise the gain of being able to tab through and select nodes is offset by a new bug/issue of "well now I lost my place within these nodes and have to go through the entire page again/reorient myself somehow".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove the section. I'm going to remove the Set tab indicies demo as well

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize I hadn't pushed my changes to this doc. Please re-review.

Comment on lines +1 to +6
---
id: Accessibility
section: extensions
subsection: topology
sortValue: 999
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a followup if it isn't as straight forward to implement for an extension, but if this file is meant to be strictly accessibility for the "Pipelines" section of topology, would be nice to include it as a tab on the Pipelines page (similar to how each PF component has the "HTML", "React", ..., "Accessibility" tabs)

@jeff-phillips-18 jeff-phillips-18 Aug 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that but I'm not sure where we would document the accessibility features for non-pipeline layouts.

Once we have it for both, the information for both will likely not need to be split between layout types. We can have it all here in one section. But for now, we only support it for pipelines.


## Pipelines layout

Pipeline task nodes and groups are keyboard accessible by default. `<TaskNode>` and `<DefaultTaskGroup>` set `tabIndex` to `0`, so you can tab to these components.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Pipeline task nodes and groups are keyboard accessible by default. `<TaskNode>` and `<DefaultTaskGroup>` set `tabIndex` to `0`, so you can tab to these components.
`<TaskNode>` and `<DefaultTaskGroup>` set `tabIndex` to `0`, so you can tab to these components and select them via both keyboard and mouse.

I think just due to the caveat mentioned about the tab reordering issue, maybe just avoiding saying these are keyboard accessible by default.

@jeff-phillips-18

Copy link
Copy Markdown
Member Author

This maybe could be a followup; deciding what roles to give each and what attributes would need to be added/documented might need a little discussion.

Added PF-4571

@jeff-phillips-18
jeff-phillips-18 force-pushed the a11y-pipelines branch 2 times, most recently from 33e7bf0 to a189990 Compare August 25, 2026 16:04
@jeff-phillips-18

jeff-phillips-18 commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

I have updated the demo to remove the Set tab indices option.

I have updated the Accessibility.md file to include the work around for tab order being lost on selection.

@nicolethoen @thatblindgeye Please take another look.

I have updated the surge at https://pipelines-a11y.surge.sh/

@thatblindgeye thatblindgeye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One reply above regarding the focus/tooltip comment I left before. Also, could we remove the "Known issues" section (still seeing it in the PR) and update the "keyboard accessible" verbiage per this comment https://github.com/patternfly/react-topology/pull/322/changes#r3853945278

@nicolethoen
nicolethoen merged commit fb4f358 into patternfly:main Aug 27, 2026
9 checks passed
@jeff-phillips-18
jeff-phillips-18 deleted the a11y-pipelines branch August 27, 2026 12:47
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.6.0-prerelease.3 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pipelines / TaskNode - Keyboard focus and screen reader support for default pipeline nodes

4 participants