Esri / calcite-design-system

A monorepo containing the packages for Esri's Calcite Design System
https://developers.arcgis.com/calcite-design-system/
Other
290 stars 75 forks source link

Research: Define pattern for components to look up ARIA labeling info automatically #2450

Closed jcfranco closed 1 year ago

jcfranco commented 3 years ago

Background

Some of our components have a label property solely to assign a11y labels on the host or internal components. I think we can simplify this by enhancing our components to look up this info without relying on a custom prop.

Ionic Framework has a util for looking up the ARIA label: https://github.com/ionic-team/ionic-framework/blob/master/core/src/utils/helpers.ts#L127-L140. Maybe we can adopt something similar and drop the label prop.

We could tackle this in two phases:

  1. define util, pattern and update doc
  2. start updating components to adopt pattern

cc @driskull @eriklharper

Desired Outcome

Introduce a utility for components to look up appropriate labels (for a11y) and no longer rely on label.

driskull commented 3 years ago

@jcfranco that utility assumes that the component is wrapped by a label or the component itself has aria-labeledby. I'm not sure that would work for a lot of the components.

It may work for some of the input/control components that we expect are wrapped by a calcite-label.

jcfranco commented 3 years ago

The util also tries to find an associated label (using for). We can use it as a base and extend it to include calcite-label and aria-label in that algorithm. This logic would work for any component, IMO.

driskull commented 3 years ago

Yes, but it means instead of just setting a label on a component the user has to create a calcite-label or label element somewhere.

jcfranco commented 3 years ago

The user can set aria-label on the component itself.

eriklharper commented 3 years ago

Assuming we support the pattern of setting aria-label directly on calcite form components, even with an automated system that also assigns an aria-label on internal elements inside the shadowRoot of said form components (to get a11y tests to pass), wouldn't that result in duplicate aria-label's being set, because you would have one on the custom element and the same one on the <input> inside the custom element's shadowRoot?

eriklharper commented 3 years ago

What's confusing to me is that when we make a shadowed component that renders an <input> in its shadowRoot, browsers, screen readers and a11y tests expect that shadowed <input> to be properly labeled even though the custom host element has an aria-label on it, or is wrapped in a label or calcite-label.

It almost feels like the cleanest solution is to not use <input> tags inside shadowRoots, but still allow typing or input, but that opens up a huge can of reinventing the wheel of basic input behaviors.

driskull commented 3 years ago

What's confusing to me is that when we make a shadowed component that renders an in its shadowRoot, browsers, screen readers and a11y tests expect that shadowed to be properly labeled even though the custom host element has an aria-label on it, or is wrapped in a label or calcite-label.

It may be a problem that the input and label are across shadow boundaries. I think they have to be within the same root node. I'm not 100% sure though. If that is the case, we may need to have both the native input and label slotted in order to keep them in the same root node and still allow styling to them.

driskull commented 3 years ago

Assuming we support the pattern of setting aria-label directly on calcite form components, even with an automated system that also assigns an aria-label on internal elements inside the shadowRoot of said form components (to get a11y tests to pass), wouldn't that result in duplicate aria-label's being set, because you would have one on the custom element and the same one on the inside the custom element's shadowRoot?

Yes it would duplicate unless we removed it on the host (which seems like a bad practice).

eriklharper commented 3 years ago

It may be a problem that the input and label are across shadow boundaries. I think they have to be within the same root node.

This is definitely true in the context of using native browser behavior for both form accessibility and native form behavior.

The problem is we're making custom elements that compose native form elements (either inside or outside a shadowRoot) without a browser API for taking control of native form behavior, which puts us in an awkward position of trying to both take advantage of native form behavior but also allowing consumers of our custom elements to set "native" properties like aria-label on them and expect it to work just like the native equivalent.

What we really need the ability to do is extend native components like input and label, but the browser support for that isn't there.

driskull commented 3 years ago

What we really need the ability to do is extend native components like input and label, but the browser support for that isn't there.

Yes that might solve the problems.

The problem is we're making custom elements that compose native form elements (either inside or outside a shadowRoot) without a browser API for taking control of native form behavior

But if we made our <calcite-input> have a required slot for a native input element, we could wrap and style the native input as well as keep support native form behavior.

We could do the same with <calcite-label> and require a slot for a native label element.


I think the above would be better than creating the native input inside of shadowRoot or creating the label ourselves inside light dom (which gives us problems in frameworks)

eriklharper commented 3 years ago

But if we made our have a required slot for a native input element, we could wrap and style the native input as well as keep support native form behavior.

Converting calcite-input to scoped would accomplish essentially the same thing without the user having to provide the native input.

We could do the same with and require a slot for a native label element.

If we decide to stick with using scoped components, there would be no need to do this since calcite-label already exposes its internal label.

eriklharper commented 3 years ago

A third option we could explore would be to completely ditch using <input> and <label> and instead just use ARIA roles on generic divs: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/textbox_role

<div role="textbox" contenteditable="true"></div>

This method (in theory) would allow assistive technologies to still work without the complexity of having to manage native inputs and labels, but the major drawback would be the burden of supporting all the behavior those components provide without actually using them. It could be worth it though since it would give us 100% control over every way that the component is supposed to work.

driskull commented 3 years ago

Converting calcite-input to scoped would accomplish essentially the same thing without the user having to provide the native input.

I think the scoped components have issues of their own and it sounds like Stencil might be moving away from them. IMO its better to have a shadowed component that can separate its internals from being exposed.

A third option we could explore would be to completely ditch using and

I don't think this would work unless both the label div and input div are in the same shadowRoot. It faces the same issue as this: It may be a problem that the input and label are across shadow boundaries. I think they have to be within the same root node.

eriklharper commented 3 years ago

I don't think this would work unless both the label div and input div are in the same shadowRoot.

We could try setting the role and contenteditable on the Host element, that way the editable div isn't inside a shadowRoot.

driskull commented 3 years ago

yeah that might work. I'm not sure a native form would pick it up based on the role though.

driskull commented 3 years ago

Would be a good research project @eriklharper

eriklharper commented 3 years ago

yeah that might work. I'm not sure a native form would pick it up based on the role though.

Right, we would have to implement our own form component in that case, most likely.

Another possibility down the road is we can support native forms with separate native-supported components, similarly to the calcite-select which was intentionally made to support the browser-native select dropdown UI. That way people have a choice whether they need native form support or not.

eriklharper commented 3 years ago

Would be a good research project @eriklharper

There's even a form role, go figure. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Form_Role

eriklharper commented 3 years ago

sounds like Stencil might be moving away from them

@driskull where did you hear this?

driskull commented 3 years ago

https://ionic.zendesk.com/hc/en-us/requests/34736

driskull commented 3 years ago

It makes sense because per spec slots are for shadow components. We should probably move away from scoped components and have all shadowed ones.

driskull commented 3 years ago

If we implement our own form with role then we will have to implement all the logic that native forms do. It's doable but it duplicates a lot of work that the browser handles already.

IMO I think that slotting inputs, textareas, labels would make it simpler to maintain native support for forms and forms within frameworks as well as get rid of some duplicate props that we have on input components just to pass them to the native input.

eriklharper commented 3 years ago

Looks like I need an Ionic account to login to that. Could you snip out the part that's relevant perhaps?

image

As per scoped versus shadow, I think ultimately shadow is better otherwise what's the point of making encapsulated styles? But I also am unsure if shadow is the end-all-be-all particularly as it relates to form stuff. If we really adopt a model where people are expected to slot-in native form elements, I just feel like the only thing that they really gain is just the brand styling? But even then, the styling is not encapsulated, so I don't ultimately see the value with that approach.

Unless I'm misunderstanding if slotted inputs and labels can still participate inside a <form>. Have you verified that a parent <form> detects slotted inputs?

driskull commented 3 years ago

I just feel like the only thing that they really gain is just the brand styling?

Yeah I think so. I think that is the point though right? We can style the native form elements and offer some stuff on top of them without having just a CSS library that requires they add a class on the native element.

But even then, the styling is not encapsulated, so I don't ultimately see the value with that approach.

The styling is still encapsulated in the shadow dom I think, right? The slotted styles would be untouchable to the end user.

Unless I'm misunderstanding if slotted inputs and labels can still participate inside a form. Have you verified that a parent

detects slotted inputs?

I don't see why they wouldn't as they are still just within the light dom.

eriklharper commented 3 years ago

We can style the native form elements and offer some stuff on top of them without having just a CSS library that requires they add a class on the native element.

Except that the styling is unrestricted because its not inside a shadowRoot, which is one of the central tenets of the library is enforcing brand consistency. I need to test this though.

driskull commented 3 years ago

Yeah, that's one of the cons to using slots, they are still in light dom so they can be styled with. I'm sure we could control most of it though.

jcfranco commented 1 year ago

Closing this issue as it won't be applicable to all components.

The original idea would only work if there's a single element to be labeled within the component. This falls apart with more complex components where props are needed to label different internal elements (e.g., slider's maxLabel, minLabel props). cc @geospatialem