Open bennypowers opened 1 year ago
As discussed in #1005
I'm grateful to @alice for her time over element chat while she participates in the Igalia WebEnginesHackfest. She graciously donated her time and (considerable) expertise to try and understand my intent with this issue and work through possible solutions.
In summary:
delegatesFocus: true
, and wraps a single <input>
. In various fora, the shorthand <fancy-input>
represents this case<fancy-input>
is not very well supported by current specsaria-hidden=true
on the shadow input, as this issue proposes, would not solve the problem as:
The proposal would therefore be amended:
IF
a shadow host delegatesfocus
AND (
is a FACE
OR has internals attached
)
THEN
allow removing focusable shadow children from the AT
have AT focus land on the host when keyboard focus is on the input
Alice recommended a workaround where
@alice reports that rough experimentation with implementing the original proposal here (permitting aria-hidden) has better-than-expected results. the screen reader does report that a text field is focused, perhaps because the <input aria-hidden="true">
is within the host with role=textbox
, so the host is the nearest ancestor with a role
for completeness, this proposal (originally, and with this comment's amendation) assumes the component developer has to hook up all the plumbing via element internals, but the developer advantage of not having to reimplement <input>
with contenteditable
remains.
@bennypowers Just to be clear, if I were to implement a custom input, the guidance would be to:
aria-hidden=true
to the shadow inputIs that correct or did I mix together two different approaches? I'm building some learning contents around this so I want to make sure I've got the best approach in my demos.
Thanks for asking @EisenbergEffect. I'm hesitant to offer anything in this thread as "good advice" for a few reasons:
That being said, as of this writing, your bullet points combine two separate and possibly incompatible solutions. This is how I understand things:
What I'm really hoping for is to gather multiple developers together with some implementers to develop advice for these components. It would be tremendous to get yourself, @nolanlawson, and @asyncLiz together with maybe @alice, @annevk and @rniwa, but perhaps that would be too onerous to arrange.
@bennypowers Thanks for the clarification! I'm happy to get together. I don't think I can add much value to the conversation itself. I do want to make sure things are properly documented and propagate the recommended approaches to the community so we can have higher quality components in the wild though.
Let's keep updating this thread as things progress. I'll use this as a resource for whatever I'm teaching so I can both show people the best current approach as well as inform them of future directions.
Thanks for the summary, @bennypowers.
Just to add some nuance, the experiment I ran was:
aria-hidden=true
being focusable in order to include it in the accessibility, so that all elements with aria-hidden=true
are hidden from the accessibility tree, undoing the fix which was added in order to prevent a poor experience from focusing on aria-hidden
elements.<input>
was hidden from the accessibility tree, and that, surprisingly, when keyboard focus was placed on the <input>
, there was an announcement that VoiceOver was on an edit field.<input>
.I'm less than optimistic about this as a viable pathway even with this mild success; I think there will be a lot of details to figure out, and I would rather we spend that energy on one of the less hacky, longer-term, more general options.
I mulled this over for a couple of hours.
<input>
. The "correct" solution is to build your own element and replace native controls, The sane solution is to just use the native control elements (<input>
, <textarea>
, <select>
). I decided to see how much would be worth to rewrite all my components the "correct" way. Simple elements seem okay, like checkbox
, role
, switch
, but for the most part, you're better off staying with the native input. Things like <input type=range>
gets some things you can't possibly polyfill, like tracking pointer position even though the pointer has left the bounding box. On a related note, a wrapped <a>
element is AFAIK, the only way to properly show some native characteristics like the tooltip and the browser context menu showing "Open link in new window". That happens because you're tapping into native controls, not emulating them.I would not risk marshalling focus state. I would not really mess with trying to manage focus between two elements with a non-exposed internal <input>
element. I agree that it could get unwieldly. I remember both Firefox and Chromium bugs dealing with FACE related to focus. They are now fixed, but I can imagine hidden input focus could unearth some bugs on some browser / screen-reader combinations.
CEs are better as containers rather than directly interactive elements. This is more of an evolution I've noticed having worked on this for some time now. Perhaps coincidence, but all the CEs that do use a role on the host are containers: toolbar
, figure
, navigation
, tabpanel
, tooltip
. All my FACEs that don't use a native control (eg: <input>
) are essentially containers as well. They set formAssociated
to use :disabled
state and disable inner elements (eg: cards as figure
) or because they expose one singular form value: (eg: listbox
).
CEs as containers allows more complexity. Given the <fancy-input>
example, if it's just a host-level interactive element, you will run into some issues trying to tap into more complex paradigms. For example, if you want to make it an combobox, with a drop down, you should include a clickable button the AXTree. That clickable button should not be inside your combobox. It should be adjacent to it. If your host-level role is combobox
, then you can't do it. But if it's role=none
, then you can present the combobox
AX Node and the button
AXNode next to each other as siblings. I can imagine other interactive icons like Clear input
or Show Password
would have similar container-like constructs.
My current, evolved interpretation of FACE is they:
:disabled
stateThe starred points are the points of issue. Yes, a label and description can be passed, but we have no ability to pass that value down to whatever element of our choosing (if any) in the Shadow Root. I need to stress that there is a difference between the textContent of a label from .labels
and the AXLabel. We cannot just take the textContent because a label can be like this:
<label for=foo>
<i class=font-icon aria-hidden=true>person</i>Name
</label>
<fancy-input name=foo></fancy-input>
That means we need more than just text, we need the actually AXLabel to be passed down. Using .labels
is not enough because textContent
isn't safe. I've experimented with observing aria-labelledby
and aria-describedby
, finding the nodes, and putting a mutation observer, cloning in the shadowRoot and then referencing them, but it's a hacky solution and considering those nodes may not exist at the time the attribute is created, it's unreliable.
I took a look at https://github.com/alice/aom/blob/gh-pages/semantic-delegate.md and it seems it could satisfy my current issues with using external labels for some FACEs. My other concern is a singular element to receive all the ARIA attributes. I haven't had the use case for it yet off the top of head, but I wouldn't like to be restricted between using aria-label
and aria-describedby
to the same shadowed element. If I have a container-like FACE, aria-label
can reference one element, but aria-controls
may refer to another. As the author, I would like to choose where I use them. Of course, that doesn't mean extra attributes or ElementInternals
properties can't be added later.
consider:
Thus, we have
contenteditable
etcaria-hidden="true"
In this case, the author's intent is for the host element (
fancy-input
) to act transparently as an input. BUT, the accessibility tree will report two nested textfields - one for the host (internals.role) and a nested one for the shadow input, even though it is marked by the author asaria-hidden
.As well, the 'hidden' input will trigger a failure for automated accessibility audits.
We should consider relaxing the rule which disallows
aria-hidden=true
androle=presentation
for elements which are within a FACE' shadow root. This would allow FACE authors to remove focusable elements from the ax tree, imperatively delegating ARIA stuff to the host.cc @annevk and @rniwa who had some pointed critique with regard to focus and keyboard events. If there was a way to overcome those critiques, could this be a viable (though less capable) alternative to @alice' semantic delegate proposal?