Skip to content

Design of bevy_a11y is BSN-unfriendly #17644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
viridia opened this issue Feb 2, 2025 · 3 comments
Open

Design of bevy_a11y is BSN-unfriendly #17644

viridia opened this issue Feb 2, 2025 · 3 comments
Labels
A-Accessibility A problem that prevents users with disabilities from using Bevy A-UI Graphical user interfaces, styles, layouts, and widgets C-Bug An unexpected or incorrect behavior S-Needs-Design This issue requires design work to think about how it would best be accomplished

Comments

@viridia
Copy link
Contributor

viridia commented Feb 2, 2025

@cart has stated that his vision for BSN involves a particular design philosophy for entities and components, specifically one in which components are made up of ordinary properties which can be merged and patched. Unfortunately, the current design for bevy_a11y is rather incompatible with this vision. I believe the reason for this is that Bevy's AccessNode is a thin wrapper around AccessKit, and attempts to mimic its API, just as the Node component mimics the API structure of Taffy. There's been some discussion of refactoring the organization of Node (#10497), and there should be a similar discussion of re-organizing AccessNode.

Currently AccessNode has several aspects which make it problematic to use in a BSN-like context:

  • The properties are private, and only accessible through methods.
  • The methods use different calling conventions depending on the type of the property.
  • All of the accessibility properties are combined together in a single ECS component.

Consider for example, the property that controls whether an entity is "disabled" (grayed out). This is set currently via the no-argument method .set_disabled() and reset by the no-argument method .clear_disabled(). A BSN template that wants to disable a widget can't simply merge in a disabled marker component, but instead must use a hook or bundle effect which calls the appropriate bevy_a11y method.

I anticipate that BSN templates are going to provide a layered approach where various patches control different aspects of a widget. For example, you might have a widget template that adds custom styling to a standard checkbox or button, using the patch mechanism. This customization process might involve accessibility properties: for example, the only difference between a checkbox and a toggle switch, from an accessibility perspective, is whether you use the "checkbox" role or the "switch" role, so the rest of the accessibility properties can be shared between the two widget types.

Because of this, I can well imagine wanting to merge together multiple BSN templates, each of which has opinions about various accessibility attributes: the template that determines the label or the role might not be the same template as the one which determines the checked or disable states. This is easy to do if these attributes are separate components, or (at least) allow overwriting of properties using patch. If setting properties requires calling of bespoke methods, however, the process becomes much more involved, as now any setting of a property becomes a read-modify-write operation.

Things get even tricker when we add interaction into the mix. Templates don't merely insert components; they also set up processes that keep those components up to date, either via a reactivity mechanism or via ECS systems that look for specific interaction components. Presumably a template that adds component X also wants to incorporate logic that keeps component X up to date. However, you have to be careful if multiple templates want to overwrite properties within same component. This argues against large, swiss-army-knife components that encompass too many concerns.

Obviously we aren't going to re-write our own version of AccessKit. Instead, I think the solution is to come up with our own idiomatic API, and then provide a transformation from that API into the AccessKit structure.

@alice-i-cecile

@viridia viridia added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Feb 2, 2025
@alice-i-cecile
Copy link
Member

Instead, I think the solution is to come up with our own idiomatic API, and then provide a transformation from that API into the AccessKit structure.

Fully agree. This is the right approach here, and we may need to eventually do something similar for taffy.

@alice-i-cecile alice-i-cecile added A-Accessibility A problem that prevents users with disabilities from using Bevy S-Needs-Design This issue requires design work to think about how it would best be accomplished A-UI Graphical user interfaces, styles, layouts, and widgets and removed S-Needs-Triage This issue needs to be labelled labels Feb 2, 2025
@alice-i-cecile
Copy link
Member

I've set out some of my thoughts on this as part of my feedback here: #16900 (comment)

@viridia
Copy link
Contributor Author

viridia commented Apr 24, 2025

@alice-i-cecile I have done some prototyping on this issue in the last few weeks, although none of what I have done is particularly elaborate.

Essentially, what I have done is treat the "label" property as a special case.

The justification for this is that labels are most often app-specific - that is, the text of the label varies from widget to widget depending on the app. This is not true of most other accessibility properties: for example the "checked" property behaves the same way for every checkbox. So in terms of separation of concerns, the label is owned by the app developer, while the other properties are owned by the widget author.

So rather than trying to solve the problem of splitting up AccessibilityNode in a general way, instead what I have done is define a new component, AccessibleLabel(String). This component can be added to any entity, but it has no effect unless the entity also has an AccessibilityNode component. An ECS system synchronizes the two: any change to AccessibleLabel causes an update to the label property of AccessibilityNode.

Because it's a separate component, it can be added as an element to the bundle independently from the other accessibility properties.

This idea can be extended to other properties if desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Accessibility A problem that prevents users with disabilities from using Bevy A-UI Graphical user interfaces, styles, layouts, and widgets C-Bug An unexpected or incorrect behavior S-Needs-Design This issue requires design work to think about how it would best be accomplished
Projects
Status: Widget-ready
Development

No branches or pull requests

2 participants