Skip to content
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

ColorPicker a11y fixes #1470

Closed
wants to merge 30 commits into from
Closed

Conversation

xman343
Copy link
Contributor

@xman343 xman343 commented Aug 2, 2023

Changes

#1148 - Editing ColorPicker examples so that they meet a11y requirements.

Testing

a11y testing will be conducted.

Docs

N/A

@xman343 xman343 added the a11y Accessibility issues (keyboard navigation, color contrast, assistive technologies, semantics, etc) label Aug 2, 2023
@xman343 xman343 self-assigned this Aug 2, 2023
@xman343 xman343 marked this pull request as ready for review August 8, 2023 13:33
@xman343 xman343 requested review from a team as code owners August 8, 2023 13:33
@xman343 xman343 requested review from mayank99 and r100-stack and removed request for a team August 8, 2023 13:33
@xman343
Copy link
Contributor Author

xman343 commented Aug 8, 2023

Will add changelog shortly.

packages/itwinui-react/src/core/Slider/Thumb.tsx Outdated Show resolved Hide resolved
@@ -140,6 +140,8 @@ export const ColorPalette = React.forwardRef((props, ref) => {
>
{label && <Box className='iui-color-picker-section-label'>{label}</Box>}
<Box
role='listbox'
Copy link
Contributor

Choose a reason for hiding this comment

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

how did you decide on using role=listbox and role=option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was the set of roles that was worked the best for the swatch array. Implementing a grid / gridcell system would have required too much refactoring. Meanwhile, giving the swatches the role of 'button' changes their appearance. 'Checkbox' didn't work for them either, because only one swatch is meant to be selected at a time.

'Listbox' / 'option' was the best structure I could find for a set of swatches. If there's another setup that you find works better, I can implement that instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense, but previously there was no role, so I'm asking why/how you decided to add one. Which specific a11y violation is this addressing? Would have been nice if you made the PR description more... descriptive.

Copy link
Contributor Author

@xman343 xman343 Aug 8, 2023

Choose a reason for hiding this comment

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

It specifically addresses the aria-input-fieldname rule, which was violated in MainExample and AdvancedExample, because the Sliders needed an accessible name.
aria-input-field-name 1
aria-input-field-name 2

Copy link
Contributor

@mayank99 mayank99 Aug 8, 2023

Choose a reason for hiding this comment

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

this looks unrelated, aria-input-field-name is only violated for the sliders, whereas i'm asking about the changes in ColorPalette.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's totally my bad. Lot of threads to keep track of!

The listbox and option roles were added to the ColorPalette and the ColorSwatches, respectively, because without those roles, it violated the 'aria-allowed-attribute' rule. This is because the Color Swatch component originally had the aria-selected attribute, despite the component not being given a role.
a11y-allowed-attr

Other roles that can take aria-selected - Tab, Row, and Grid - either weren't appropriate for ColorPalette & ColorSwatch, or (in the case of Grid) would have required a more extensive refactoring of the code than I figured would be necessary. (More info here.)

@@ -287,6 +287,8 @@ export const ColorBuilder = React.forwardRef((props, ref) => {
</Box>

<Slider
aria-label='Hue slider'
id='Slider'
Copy link
Contributor

Choose a reason for hiding this comment

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

ids should not be hardcoded like this. why do you need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There wasn't a way to set the IDs for these nodes from example.

Copy link
Member

Choose a reason for hiding this comment

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

Do you need to give an aria-label to the Slider div? Instead, I believe you can pass an aria-label to the thumbs directly (https://github.com/iTwin/iTwinUI/pull/1470/files#r1287238103). Then there is no need for an id.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • ids are meant to be globally unique. Hardcoding a static id will result in every instance of this component getting the same clashing id.
  • this id is getting applied on the top-level container div, so i want to reiterate my question: why do you need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll follow Rohan's suggestion.

return (
<ColorSwatch
role='option'
aria-label={swatchLabel}
Copy link
Member

@r100-stack r100-stack Aug 8, 2023

Choose a reason for hiding this comment

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

Should we have a default aria-label that the ColorSwatch adds? Like swatchLabel could be calculated inside ColorSwatch. If the user provided an aria-label to ColorSwatch, we use it. But if not, we use swatchLabel as the aria-label.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd like to implement this, although it might be a bit too involved of a refactor for the scope of this PR. I'll see what I can do.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, it is very small change, sorry if I didn't explain it clearly in my comment.

I meant like instead of calculating swatchLabel on L153, you can do it in ColorSwatch.tsx instead. Because you are passing the color to ColorSwatch on L159.

Then in ColorSwatch.tsx, you can do something like this:
image

Copy link
Member

@r100-stack r100-stack Aug 8, 2023

Choose a reason for hiding this comment

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

Ohh, actually colorString (L42-48 of the screenshot) already calculates the string representation of the color. You could try reusing that instead of swatchLabel.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

colorString return's the color's hex value, which wouldn't be very understandable. That's one of the main reasons I made swatchLabel its own thing - it returns the color's value in HSL form, which (in my experience) is easier to learn to interpret than the hex form.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched from hsl to hex

Copy link
Contributor

@mayank99 mayank99 Aug 9, 2023

Choose a reason for hiding this comment

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

please read the comment again, the suggestion is not to switch from hsl to hex, but to use colorString which preserves the source value (can be hex or hsl or whatever string the color was instantiated with)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had tried using colorString for the swatch label already, but I couldn't figure out how to use it in ColorPalette.tsx. When I try using colorString in ColorSwatches.tsx, its original scope, ColorPickerBasicExample throws an aria-allowed-attribute error for the selected color swatch on top.
image
This is because that swatch doesn't have a role, since the roles are distributed in ColorPalette.tsx. I could try giving that swatch its own role in the examples file, but it's hard for me to tell what role is appropriate. I could also continue to assign the swatch labels the way I have been, even if it is redundant.

Copy link
Contributor

Choose a reason for hiding this comment

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

should be fixable if you use visually-hidden text instead of aria-label

<Box
  className={cx('iui-color-swatch', { 'iui-active': isActive }, className)}
  // ...
  {...rest}
>
  <VisuallyHidden>{colorString.toUpperCase()}</VisuallyHidden>
</Box>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. It works!

examples/ColorPicker.advancedPopover.tsx Outdated Show resolved Hide resolved
@mayank99 mayank99 mentioned this pull request Aug 9, 2023
6 tasks
@FlyersPh9 FlyersPh9 marked this pull request as draft August 21, 2023 13:48
@mayank99 mayank99 closed this Sep 6, 2023
@mayank99 mayank99 deleted the xander/color-picker-a11y-improvements branch September 6, 2023 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a11y Accessibility issues (keyboard navigation, color contrast, assistive technologies, semantics, etc)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants