I am currently working on the VoiceOver experience for a custom web component and I have come across an inconsistency in the VoiceOver behavior.
My custom web component functions as a dropdown select with options to choose from. In the below scenarios, I describe the varying behavior of VoiceOver. Thanks in advance for reading this.
Scenario 1:
The dropdown has 3 available options to choose from: Option 1, Option 2, Option 3
<custom-select label="Dropdown Label">
<custom-option value="Option 1">Option 1</custom-option>
<custom-option value="Option 2">Option 2</custom-option>
<custom-option value="Option 3">Option 3</custom-option>
</custom-select>
You select Option 2. This closes the pop-up and updates the dropdown component to display the option you've chosen
You then re-open the dropdown, focus jumps to the previously selected option (in this case Option 2, which now has a checkmark next to it), and VoiceOver says:
Checkmark. Option 2. Clickable. You are currently on a text element.
This is the VoiceOver behavior you will consistently get for any option that is not the last option in the list. The next scenario explains what happens there...
Scenario 2:
Again, 3 available options to choose from: Option 1, Option 2, Option 3
You select the last option, Option 3. This closes the pop-up and updates the dropdown component to display the option you've chosen
You then re-open the dropdown, focus jumps to the previously selected option (the checkmarked Option 3), and VoiceOver says:
Option 1. Option 2. Checkmark. Option 3. Clickable. You are currently on a text element.
So only when you choose the last option, the VoiceOver decides to read all of the options before getting to the one that has focus...
Relevant HTML:
The parent component contains a button which toggles a display div containing the dropdown options (the "slot" for nested components). I've stripped it down to the relevant attributes, classes and directives for presentation's sake (written in Svelte.js):
<button
type="button"
aria-required={ariaRequired}
aria-haspopup="listbox"
aria-label={ariaLabel}
aria-disabled={ariaDisabled}
aria-expanded={ariaExpanded}
on:click={toggleOptions}>
{display}
</button>
<div
tabindex="-1"
class:hidden={optionSelectVisable === false}>
<slot />
</div>
And then a nested, option component looks like this:
<div
on:click={dispatchOption}
role="option"
value={value}
id="option-{inputID}"
aria-selected={ariaSelected}
aria-disabled={ariaDisabled}
tabindex={tabindex}>
<div>
<custom-icon
type="checkmark"
aria-label="checkmark"
class:hidden={displayCheckmark === false}/>
</div>
<div>
<slot />
</div>
</div>
Rendered HTML (example w/ Option 1 checked):
<custom-select label="Dropdown Label">
→#shadow-root
<custom-option value="Option 1">
↓#shadow-root
<div
role="option"
aria-selected="true"
aria-disabled="false"
value="Option 1"
tabindex="0">...</div>
"Option 1"
<custom-option>
<custom-option value="Option 2">
↓#shadow-root
<div
role="option"
aria-selected="false"
aria-disabled="false"
value="Option 2"
tabindex="0">...</div>
"Option 2"
<custom-option>
<custom-option value="Option 3">
↓#shadow-root
<div
role="option"
aria-selected="false"
aria-disabled="false"
value="Option 3"
tabindex="0">...</div>
"Option 3"
<custom-option>
</custom-select>
Any help is greatly appreciated. Thank you.
Related
I have a page structured as following.
<div>
<div>
<h2>header</h2>
<button>close button</button>
</div>
<div>
<div>some info</div>
<div roll='groub' aria-label='some general text about which checkbox is selected'>
<div>
<input type="checkbox" id="scales" name="scales" checked aria-label="scales">
<label for="scales">Scales</label>
</div>
<div>
<input type="checkbox" id="horns" name="horns" aria-label="horns">
<label for="horns">Horns</label>
</div>
</div>
</div>
</div>
I have my screen reader on and want to be able to tab from each ui component, and screen reader reads it out for me as I press tab key.
when it reaches to the div with roll of group, then it reads out the aria label of that div element plus all of its children!
What I want is to read only the aria-label of the div with roll of group and stops. Then only reads the aria label of the checkboxes when I tab into them.
any suggestion on what roll I should define for input elements so they get announced only when they are focused by tab key ?
The question should be how to use groups to build a screen reader friendly form.
When it comes to checkboxes or radio buttons, a group is the correct way to go, as it will bind the overall question to the controls, which are in tab order.
So that would be something like
Close button, button
Tab
some general text about which checkbox is selected, group
Scales, checkbox, unchecked
The group itself will not be focussed. But when focussing the first checkbox, the screen reader will announce the group’s name once. This will help screen reader users choose the right one. Once they change to the next checkbox, the group’s name will not get announced.
This is how it should work, since forms are used mainly by means of Tab and the user needs to get the info about what the checkboxes are about.
To follow the First Rule of Using Aria, you should use a <fieldset> element with a <legend>.
If you can use a native HTML element [HTML51] or attribute with the semantics and behavior you require already built in, […] then do so.
You should not provide aria-label attributes when there is a visual label already. It is correctly used with a for attribute, hence is already providing an accessible name to the checkbox.
<div>
<div>
<h2>header</h2>
<button>close button</button>
</div>
<div>
<div>some info</div>
<fieldset>
<legend>some general text about which checkbox is selected</legend>
<div>
<input type="checkbox" id="scales" name="scales">
<label for="scales">Scales</label>
</div>
<div>
<input type="checkbox" id="horns" name="horns">
<label for="horns">Horns</label>
</div>
</fieldset>
</div>
</div>
I have a page that uses Bootstrap 4 with a single input control that was working okay. My problem is when I add help it adds it on the same line rather than the next line beneath the control as I would expect.
My suspicion is it that it is a problem with input-group/form-group. Im finding the generally very good Bootstrap documenentation very unclear on how these two things fit together since Im using inputgroups as the field label.
This is the html
<div class="input-group">
<div class="input-group-prepend">
<label for="undoLocation" id="undoLocationlabel" class="input-group-text">
Find songs
</label>
</div>
<select class="custom-select" name="undoLocation" id="undoLocation" aria-describedby="undoLocationHELP">
<option value="0">
that are currently in the selected locations
</option>
<option selected="selected" value="1">
that were originally in the selected locations
</option>
</select>
<small id="undoLocationHELP" class="form-text text-muted">
When files have been moved by SongKong you can use this option to find files currently in the selected all location or find files that were originally in the selected location
</small>
</div>
and I have created a jsfiddle.
https://jsfiddle.net/paultaylor/g64v96fy/1/
According to Bootstrap discussion:
Block help text—for below inputs or for longer lines of help text—can be easily achieved with .form-text. This class includes display: block and adds some top margin for easy spacing from the inputs above.
However, you added it to a parent element with a input-group class, by nature will display with the group, inline. Additionally, you used an HTML default inline tag <small> which also displays, by nature, inline.
Per specs:
The small element should not be used for extended spans of text, such as multiple paragraphs, lists, or sections of text. It is only intended for short runs of text...
Even if you do what's recommended and wrap it in a <p> tag, the parent still defines the style, and input-groups by nature are inline.
So, it's recommended to move it outside of the div.
<div class="input-group">
<div class="input-group-prepend">
<label for="undoLocation" id="undoLocationlabel" class="input-group-text">
Find songs
</label>
</div>
<select class="custom-select" name="undoLocation" id="undoLocation" aria-describedby="undoLocationHELP">
<option value="0">that are currently in the selected locations</option>
<option selected="selected" value="1">that were originally in the selected locations</option>
</select>
</div>
<p id="undoLocationHELP" class="form-text text-muted">
When files have been moved by SongKong you can use this option to find files currently in the selected all location or find files that were originally in the selected location
</p>
So, I have the IssuesList component, which is the list of issues that I get using ajax and github api, and DevStatus component, which sort of wraps the list up and contains all the logic, triggers state changes by two radiobuttons and so on.
My problem: When I click on one of the radiobuttons, the DevStatus component won't change state if the click was on the text inside the radiobutton. And when I click on the corners of the radiobuttons, the blue areas without text, the state changes perfectly.
Here's the structure of the radiobuttons:
<div className="btn-group" data-toggle="buttons">
<label className="btn btn-primary active"
onClick={this.onChangeRadioButton.bind(this)}
id={this.CLOSED_ISSUE_ID}>
<input type="radio" name="options"
autoComplete="off"
id={this.CLOSED_ISSUE_INPT_ID}
onChange={this.onInputChange.bind(this)} /> Closed Issues
</label>
<label className="btn btn-primary"
onClick={this.onChangeRadioButton.bind(this)}
id={this.OPEN_ISSUE_ID}>
<input type="radio" name="options"
autoComplete="off"
id={this.OPENED_ISSUE_INPT_ID}
onChange={this.onInputChange.bind(this)} /> Open Issues
</label>
</div>
Here's the codepen with the code and here's the full page view so you could better see and understand what I'm talking about.
Please, open the full page view and try to click on parts of the button that contain text and on ones that don't and you'll notice that as long as you click on parts without text - the state changes and if you click on text itself - the state doesn't change at all.
Could you please help me with that problem?
PS: removing onChange from the input element is not the solution.
Update 1
If you go to DevTools and inspect the radiobutton element, you'll see that inside the label tag there're input and weird span elements. The span element is not in the code I wrote, did React automatically add that? For some reason, the onClick event listener is not applied to those input and span elements.
Update 2
I've tried to add click event listener to the radiobutton in the console of dev tools and tried to figure out the target of the clicked element. When I click on the text - it is the span element and when I click on place without text - it is the label element and that's why the click event is not working.
Can my problem be solved using dangerouslySetInnerHTML, so that it won't create the unnecessary span?
Could you tell me please how to solve that?
React is creating a span because your text is not in any div. Also it would create a span if there was any white space (but in your case this is because there is no div around your text).
But the real problem here is the way you check your event. You need to check e.currentTarget instead of e.target
Then no need to use the ugly dangerouslysetinnerhtml!
React appeared to sometimes be adding span tags around text, no matter if there are the free white-spaces or not. The spans didn't allow the onClick event to fire when they were clicked on.
So, to force React not to render the spans, the dangerouslySetInnerHTML may be used:
noSpanRender(text) {
return { __html: `<input type='radio' name='options' autoComplete='off'/>${text}` };
}
render() {
return (
<div className="dev-status-page col-centered">
<div className="graphs">
<h1 className="text-center page-header">
Our Recent Closed and Opened Issues from GitHub
</h1>
</div>
<div className="issues col-centered">
<div className="btn-group" data-toggle="buttons">
<label className="btn btn-primary active"
onClick={this.onChangeRadioButton.bind(this)}
id={this.CLOSED_ISSUE_ID}
dangerouslySetInnerHTML={this.noSpanRender('Closed Issues')} />
<label className="btn btn-primary"
onClick={this.onChangeRadioButton.bind(this)}
id={this.OPEN_ISSUE_ID}
dangerouslySetInnerHTML={this.noSpanRender('Open Issues')} />
</div>
<IssuesList issues={this.state.issues} />
</div>
</div>
)
}
It was vital to avoid those span elements inside the input tag, so using dangerouslySetInnerHTML finally helped.
I'm using a template based on Bootstrap 3 and there is a weird display issue with Firefox for file input elements: basically the click area covers a huge part of the page, which means that anything around them cannot be clicked without triggering a file upload.
Here is what I mean by large area (with Firebug, based on the <input type="file"> element):
While with Chrome it's fine:
And with IE it's also fine.
Here is the link to the theme page where you see the issue (only happening to the first type of "advanced file input", where the input element itself is displayed): http://www.keenthemes.com/preview/metronic_admin/components_form_tools.html
And here is the code for the file display:
<div class="fileinput fileinput-new" data-provides="fileinput">
<div class="input-group input-large">
<div class="form-control uneditable-input span3" data-trigger="fileinput">
<i class="fa fa-file fileinput-exists"></i>
<span class="fileinput-filename"></span>
</div>
<span class="input-group-addon btn default btn-file">
<span class="fileinput-new">Select file</span>
<span class="fileinput-exists">Change</span>
<input type="file" name="documents-0">
</span>
Remove
</div>
</div>
Any idea how I can force the input element click zone to stay within its limits on Firefox?
Thanks in advance!
With the following markup:
<div class="paymentOption">
<input type="radio" name="paymentOption" value="1" />
<span class="marginLeft10">1-time payment using a different credit card</span>
</div>
Using CSS only, how do we select the div that contains a selected radio button? (is this possible?)
This is currently impossible with CSS. You can use jQuery though:
http://api.jquery.com/has-selector/