-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Accessibility] Improve visualization legends accessibility #14505
Conversation
Jenkins, test this |
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.
It's so great to see our visualizations becoming more accessible! I had some suggestions and questions for your consideration.
@@ -3,10 +3,13 @@ | |||
type="button" | |||
ng-click="toggleLegend()" | |||
class="kuiCollapseButton legend-collapse-button" | |||
aria-label="Toggle legend" |
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.
VoiceOver doesn't announce this label because of this aria-hidden logic introduced in #13573. I think that the warning is still useful, but perhaps we should just remove aria-hidden="true"
from the legend?
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.
Good point, I will remove it, but therefor I would like to add an aria-label
introducing the legend as such. That would also partly solve the issue you mentioned below, regarding missing context for the user with just legend values.
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.
Done
@@ -18,6 +21,10 @@ | |||
|
|||
<div class="legend-value-container"> | |||
<div | |||
kbn-accessible-click | |||
data-label="{{legendData.label}}" |
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.
What does this attribute do?
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.
Currently, the label doesn't indicate to the screen reader what will happen if the user clicks the button. Is there an elegant way to add an invisible label that says something like "Click for filtering options"? Maybe we can associate it with aria-describedby
? Do you think this will be helpful?
Also, if the label is a value like "181", then the screen reader will just read this number aloud, which doesn't provide enough context for me to make sense of it. What if we added a noun to the end of this value? For example, if the aggregation was on "bytes", we could read aloud:
{{ `${legendData.label} + ${legendData.values.aggConfig.params.field.name}` }}
Which would cause the screen reader to announce "181 bytes". What do you think?
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.
The attribute itself is required by the handlers in the visualization, that are doing the highlight
and unhighlight
to find the actual value. Not ideal imho, but that's the way it works atm.
I will add a description for what the label triggering does, that's a very good idea.
I think extending the label with the fieldname, won't always work as smooth as it did in your example :-) So I would rather add this to the description like: "Filtering bytes fields for this value"
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 added it to the label instead, so you really can't miss it. I didn't add aria-expanded
since that would require we can reference the legend options via aria-controls
. But they use ng-if
and not ng-show
at the moment, so they are really removed from DOM and we cannot reference them when hidden. I also don't want to use ng-show
in that place, since imho the options contain a significant amount of DOM nodes and having a dashboard with multiple visualizations on it, which several legend items, would render a significantly higher amount of DOM nodes with ng-show
.
So I decided to not use aria-expanded
in that case.
kbn-accessible-click | ||
data-label="{{legendData.label}}" | ||
ng-focus="highlight($event)" | ||
ng-blur="unhighlight($event)" | ||
ng-click="showDetails = !showDetails" |
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.
Once the user opens these options, it's easy to get lost in the colorpicker. I know we don't want to spend time fixing this in this PR, but I think it would be helpful to let the user close the options by hitting ESC. This way if s/he gets lost in the color picker we've provided an easy way out. We just need to extract lines 22 through 75 into their own directive, and add a keypress handler there which sets showDetails
to false. The challenge might be setting focus back on the button once the filtering options have been closed. What do you think?
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 think the color picker is not really accessible the way it is even after that PR. But I don't think just adding Escape would be enough. We still would need to think about color names, proper arrow navigation, etc... So I would rather replace this in the more or less near future, by using a standardized color picker from the UI framework instead and put the a11y effort into this. It would also come with the benefit, of being able to freely assign a color.
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.
Yeah, I hear you... I was just thinking of it as a temporary solution, since it's so easy to get stuck inside the color picker options and not know that the only way to get out of them is to backtrack all the way back to the button and then click it again.
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.
Done
> | ||
<span class="kuiIcon fa-search-minus"></span> | ||
</button> | ||
</div> | ||
|
||
<div class="legend-value-color-picker"> | ||
<div class="legend-value-color-picker" role="listbox"> | ||
<span id="{{legendId}}ColorPickerDesc" class="kuiScreenReaderOnly">Set color for value {{legendData.label}}</span> |
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.
Can we indent this?
<span
id="{{legendId}}ColorPickerDesc"
class="kuiScreenReaderOnly"
>
Set color for value {{legendData.label}}
</span>
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.
What if I would say "no"? Yes of course we can :-)
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.
Then you better have a good reason! :D
const colors = $scope.uiState.get('vis.colors') || {}; | ||
if (colors[label] === color) delete colors[label]; | ||
else colors[label] = color; | ||
$scope.uiState.setSilent('vis.colors', null); | ||
$scope.uiState.set('vis.colors', colors); | ||
$scope.uiState.emit('colorChanged'); | ||
if (event) { | ||
event.currentTarget.blur(); |
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.
Out of curiosity, why are we doing this?
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.
Good question. I was tinged with the slight idea, of not having the color button keeping the focus ring and it's then selected state. But honestly, we don't do this anywhere else, and again I want to replace this by the unified color picker in the future, so yeah I will throw it out for this PR.
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.
LGTM
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 like it.
This works a lot more intuitively than I thought it would. Nice too that you can jump to filter bar and continue editing filters there.
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.
Fantastic! Thanks for adding the ESCAPE handler -- I really think it's a great escape hatch (sorry).
I had one small nitpicky comment but it's probably unnecessary so feel free to disregard it.
You mentioned this:
So I would rather add this to the description like: "Filtering bytes fields for this value"
But I didn't see this change -- not sure if this was intentional? If so, that's fine, it's a minor thing anyway, but just wanted to check.
* This will close the details panel of this legend entry when pressing Escape. | ||
*/ | ||
$scope.onLegendEntryKeydown = function (event, scope) { | ||
if (event.keyCode === keyCodes.ESCAPE) { |
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 might be overthinking this but maybe we should check if showDetails is true first? Maybe someday in some bizarre future, something further up the DOM tree will care about the ESCAPE key being pressed and will be sad that it's being swallowed up here.
$scope.onLegendEntryKeydown = function (event, scope) {
if (event.keyCode === keyCodes.ESCAPE) {
if (scope.showDetails) {
event.preventDefault();
event.stopPropagation();
scope.showDetails = false;
}
}
};
@cjcenizal actually it was intentional. Sorry for not commenting on that :/ That problem is, that the field is nested deep within the aggregation information and also there doesn't need to be a field (e.g. if you are doing a filters aggregation). Since we currently have no service (or I didn't found any) to extract the field information from the visualization legend data, but would have to implement that logic all over again, I decided it's not worth it at the moment. |
…14505) * Make legend toggle button accessible, fix elastic#11843 * Make legend filter buttons accessible * Highlight chart segments when focusing legend, fix elastic#11845 * Make legend color picker accessible * Remove aria-hidden from legend * Use proper indentation * Remove bluring color button again * Close legend details on pressing escape * Add hint about the action * Only capture escape press when details are opened
…14606) * Make legend toggle button accessible, fix #11843 * Make legend filter buttons accessible * Highlight chart segments when focusing legend, fix #11845 * Make legend color picker accessible * Remove aria-hidden from legend * Use proper indentation * Remove bluring color button again * Close legend details on pressing escape * Add hint about the action * Only capture escape press when details are opened
Release Note: Improve the keyboard accessibility of visualization legends.
Improve the accessibility of the regular visualization legends:
This should make the legend accessible for now. We could definitely improve the accessibility especially in the color picker a lot, but I hope that we can replace the color picker at some point with the ui framework color picker, so I wouldn't spend now too much time into improving the existing color picker as it is basically working now.