fix(a11y/forms) correctly label <Select> components
it's important for some screen readers to have inputs and labels correctly linked together via html attributes. Without doing this, some users can have trouble knowing what the form input is about. Select inputs had visible labels but they were actually not tied to their inputs. Bottom labels too, where additional info is shown, or I guess errors also. Now the label, additional info and potential errors are correctly linked.
This commit is contained in:
parent
506e0523c6
commit
9d7f1dc2dc
|
@ -28,6 +28,12 @@ interface ISelectProps {
|
|||
*/
|
||||
error?: boolean;
|
||||
|
||||
/**
|
||||
* Id of the <select> element.
|
||||
* Necessary for screen reader users, to link the label and error to the select.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Label to be displayed above the select.
|
||||
*/
|
||||
|
@ -140,6 +146,7 @@ const Select = ({
|
|||
className,
|
||||
disabled,
|
||||
error,
|
||||
id,
|
||||
label,
|
||||
onChange,
|
||||
options,
|
||||
|
@ -149,11 +156,17 @@ const Select = ({
|
|||
|
||||
return (
|
||||
<div className = { classes.container }>
|
||||
{label && <span className = { cx(classes.label, isMobile && 'is-mobile') }>{label}</span>}
|
||||
{label && <label
|
||||
className = { cx(classes.label, isMobile && 'is-mobile') }
|
||||
htmlFor = { id } >
|
||||
{label}
|
||||
</label>}
|
||||
<div className = { classes.selectContainer }>
|
||||
<select
|
||||
aria-describedby = { bottomLabel ? `${id}-description` : undefined }
|
||||
className = { cx(classes.select, isMobile && 'is-mobile', className, error && 'error') }
|
||||
disabled = { disabled }
|
||||
id = { id }
|
||||
onChange = { onChange }
|
||||
value = { value }>
|
||||
{options.map(option => (<option
|
||||
|
@ -167,7 +180,9 @@ const Select = ({
|
|||
src = { IconArrowDown } />
|
||||
</div>
|
||||
{bottomLabel && (
|
||||
<span className = { cx(classes.bottomLabel, isMobile && 'is-mobile', error && 'error') }>
|
||||
<span
|
||||
className = { cx(classes.bottomLabel, isMobile && 'is-mobile', error && 'error') }
|
||||
id = { `${id}-description` }>
|
||||
{bottomLabel}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
@ -163,6 +163,7 @@ class DeviceSelector extends Component<IProps> {
|
|||
|
||||
return (
|
||||
<Select
|
||||
id = { this.props.id }
|
||||
label = { this.props.t(this.props.label) }
|
||||
onChange = { this._onSelect }
|
||||
options = { options.items }
|
||||
|
|
|
@ -351,6 +351,7 @@ class VideoDeviceSelection extends AbstractDialogTab<IProps, State> {
|
|||
bottomLabel = { parseInt(currentFramerate, 10) > SS_DEFAULT_FRAME_RATE
|
||||
? t('settings.desktopShareHighFpsWarning')
|
||||
: t('settings.desktopShareWarning') }
|
||||
id = 'more-framerate-select'
|
||||
label = { t('settings.desktopShareFramerate') }
|
||||
onChange = { this._onFramerateItemSelect }
|
||||
options = { frameRateItems }
|
||||
|
|
|
@ -100,6 +100,7 @@ class StreamKeyPicker extends PureComponent<IProps> {
|
|||
return (
|
||||
<div className = 'broadcast-dropdown dropdown-menu'>
|
||||
<Select
|
||||
id = 'streamkeypicker-select'
|
||||
label = { t('liveStreaming.choose') }
|
||||
onChange = { this._onSelect }
|
||||
options = { dropdownItems }
|
||||
|
|
|
@ -167,6 +167,7 @@ class MoreTab extends AbstractDialogTab<Props, any> {
|
|||
key = 'maxStageParticipants'>
|
||||
<div className = 'dropdown-menu'>
|
||||
<Select
|
||||
id = 'more-maxStageParticipants-select'
|
||||
label = { t('settings.maxStageParticipants') }
|
||||
onChange = { this._onMaxStageParticipantsSelect }
|
||||
options = { maxParticipantsItems }
|
||||
|
|
|
@ -212,6 +212,7 @@ class ProfileTab extends AbstractDialogTab<IProps, any> {
|
|||
return (
|
||||
<Select
|
||||
className = { classes.bottomMargin }
|
||||
id = 'more-language-select'
|
||||
label = { t('settings.language') }
|
||||
onChange = { this._onLanguageItemSelect }
|
||||
options = { languageItems }
|
||||
|
|
Loading…
Reference in New Issue