-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Update HeightControl component to label inputs #48498
Update HeightControl component to label inputs #48498
Conversation
The current method of using a fieldset for the HeightControl inputs leaves neither of them with a functioning accessible name. This change ensures both the text field and the range slider are named, and semantically linked (with the caveat that aria-controls has little support as of now).
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @andrewhayward! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
Potentially part of a larger body of work relating to #42630. |
👍 Let me try to get to this later today. Thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @andrewhayward , thank you for opening this PR and working on what is definitely a complex task.
I'm going to recap the changes in this PR, mainly to confirm with you whether I'm understanding correctly what you're proposing, and to help other reviewers:
HeightControl
renders both aUnitControl
and aRangeControl
component, which are used as alternative forms of input to edit the same underlying value. TheHeighControl
component takes care of keepingUnitControl
andRangeControl
's values in sync.- Currently on
trunk
,HeightControl
uses a combination offieldset
/legend
to label the input fields. - Given that
UnitControl
and aRangeControl
are alternative to each other, they fall in the category of what you have previously described as "Redundant Inputs". - In this scenario, you're proposing of dropping the
fieldset
/legend
approach, and instead:- labelling the "primary" input (ie.
UnitControl
) in a more traditional way (i.e withlabel
element and thefor
attribute) - labelling the "redundant" input (ie.
RangeControl
) via thearia-labelledby
attribute, and use thearia-controls
attribute to signal that the redundant input controls the same value as the "primary" input.
- labelling the "primary" input (ie.
Typically, I've seen the aria-controls
attribute being used for composite widgets like comboboxes — this approach is definitely interesting, and one that we haven't explored yet! I'd be very curious to test it further across different browsers / OSs / screen readers. Have you already done some testing?
The most interesting and challenging aspect of this approach is in evaluating its scalability / composability. A few considerations:
UnitControl
itself renders multipleinput
elements, in a "composite" way (ie. the quantity and the unit inputs)RangeControl
can render a redundant input field itself (when thewithInputField
prop is set totrue
)
Would this approach still work well when wrapping around (or nesting under) other similar composite and/or redundant components?
'inspector-range-control' | ||
); | ||
|
||
return idProp || id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to allow for empty string as the value of isProp
?
return idProp || id; | |
return idProp ?? id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to allow for empty string as the value of
idProp
?
I'm not sure as I can think of a scenario where an empty string would be an acceptable value. Currently, the range control auto-generates its own ID, and always uses it. The change just means that it can be overridden, and if the range control expects a value for any reason, an empty string might cause confusion.
But this is not a strongly held opinion! I'd be happy with your suggested change if those more familiar with the code than me think it appropriate. I took it from a current pattern that exists in a few places already (to the point that it might be worth adding a useOverridableInstanceId
function in @wordpress/compose
!).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, this makes sense to me!
const id = useInstanceId( HeightControl, 'inspector-height-control' ); | ||
const labelId = `${ id }__label`; | ||
const inputId = `${ id }__input`; | ||
const rangeId = `${ id }__range`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way of generating IDs looks good. An alternative could be to simply use the recently introduced useId
hook to generate three separate IDs? Although I'm definitely fine keeping the current approach, given it tests well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way of generating IDs looks good. An alternative could be to simply use the recently introduced
useId
hook to generate three separate IDs? Although I'm definitely fine keeping the current approach, given it tests well.
I just took the useInstanceId
pattern found elsewhere and extended it, to be honest, so I don't feel strongly either way. Perhaps worth noting though that even the useId
docs say this about related elements:
If you need to give IDs to multiple related elements, you can call
useId
to generate a shared prefix for them
So even React's expectation seems to be that in this situation you'd generate one stem and create multiple IDs from it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, thank you for the explanation
<fieldset className="block-editor-height-control"> | ||
<BaseControl.VisualLabel as="legend"> | ||
<div className="block-editor-height-control" id={ id }> | ||
<BaseControl.VisualLabel as="label" for={ inputId } id={ labelId }> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading at the doc, it looks like BaseControl.VisualLabel
is not meant to be used to render an actual label
(even though it's possible to do so, through the as
prop) — from the docs:
BaseControl.VisualLabel
is used to render a purely visual label inside aBaseControl
component.It should only be used in cases where the children being rendered inside BaseControl are already accessibly labeled, e.g., a button, but we want an additional visual label for that section equivalent to the labels
BaseControl
would otherwise use if thelabel
prop was passed.
We could take one of these approaches:
- keep the current solution (which means that we should probably update the
BaseControl.VisualLabel
docs?) - render an additional
<label />
element (and make it visually hidden). At the point, we could remove theas
prop onBaseControl.VisualLabel
and use it only for aesthetical reasons - remove
BaseControl.VisualLabel
and replace it with a simple<label />
(although we'd need to somewhat style it).
Maybe @mirka has some more insights on what would be the better approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see how we could enhance the BaseControl
component itself so that it can support this kind of labeling pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd need to spend more time looking at how BaseControl
works to offer much feedback around any of this!
This all looks correct, yes.
I've done some limited testing, yes. It is my understanding though that only JAWS (don't have access currently) has an option for jumping to controlled elements, and even then doesn't announce the presence of any such relationships by default.
It should do, yes (caveat: my awareness of existing components is somewhat cursory!), as long as IDs and |
@andrewhayward Sorry for taking so long to review this. Could you please refresh the PR? Thanks. |
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @andrew-palapa. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrewhayward I think this one looks good, can we get it merged? Gave it a test in storybook, all looks good.
I totally forgot about this PR! Thanks for chasing, I'll get it sorted out. |
Closing in favour of #63761, a copy of this PR associated to a branch on the Gutenberg repository for easier collaboration from all contributors. I'll make sure to credit this PR's author and collaborators in case the clone PR gets merged. |
What?
Adds accessible names to both inputs in the
HeightControl
component, replacing thefieldset
wrapper and usingaria-controls
instead.Why?
Currently,
HeightControl
uses a<fieldset>
to label the input fields, but this doesn't provide an accessible name for either. Both fields should have an appropriate name.How?
RangeControl
component to accept an optional ID.<fieldset>
/<legend>
combination with a<label>
, pointing at the text input, to ensure it has an accessible name.<label>
with anaria-labelledby
to get its own accessible name.aria-controls
attribute, pointing at the text input (while acknowledging thataria-controls
has little support at the time of writing).Testing Instructions
npm run storybook:dev
HeightControl
componentTesting Instructions for Keyboard
Nothing has functionally changed, so both inputs should remain as expected