Skip to content

Commit 67c17c8

Browse files
(Input): improve a11y
1 parent b3d9288 commit 67c17c8

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

src/scripts/Input.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, {
2+
useId,
23
ReactElement,
34
InputHTMLAttributes,
45
KeyboardEvent,
@@ -37,12 +38,19 @@ function useInitComponentStyle() {
3738
/**
3839
*
3940
*/
40-
const InputAddon = ({ content }: { content: string }) => (
41+
const InputAddon = ({
42+
content,
43+
id,
44+
}: {
45+
content: string;
46+
id: string | undefined;
47+
}) => (
4148
<Text
4249
tag='span'
4350
className='slds-form-element__addon'
4451
category='body'
4552
type='regular'
53+
id={id}
4654
>
4755
{content}
4856
</Text>
@@ -155,32 +163,52 @@ export const Input = createFC<InputProps, { isFormElement: boolean }>(
155163
prevValueRef.current = e.target.value;
156164
});
157165

166+
const labelId = useId();
167+
168+
const fallbackLabelFor = useId();
169+
const labelFor = id ?? fallbackLabelFor;
170+
171+
const originalPreAddonId = useId();
172+
const preAddonId = addonLeft ? originalPreAddonId : undefined;
173+
174+
const originalPostAddonId = useId();
175+
const postAddonId = addonRight ? originalPostAddonId : undefined;
176+
177+
const labelledBy = [labelId, preAddonId, postAddonId]
178+
.filter((id) => id !== undefined)
179+
.join(' ');
180+
181+
const errorId = useId();
182+
158183
const { isFieldSetColumn } = useContext(FieldSetColumnContext);
159184
const inputClassNames = classnames(
160185
className,
161186
bare ? 'slds-input_bare' : 'slds-input'
162187
);
163188
const inputElem = readOnly ? (
164189
<Text
165-
id={id}
190+
id={labelFor}
166191
type='regular'
167192
category='body'
168-
className='slds-form-element__static'
193+
aria-labelledby={labelledBy}
169194
>
170195
{value}
171196
</Text>
172197
) : (
173198
<input
174199
ref={inputRef}
175200
className={inputClassNames}
176-
id={id}
201+
id={labelFor}
177202
type={type}
178203
value={value}
179204
defaultValue={defaultValue}
180205
readOnly={htmlReadOnly}
181206
{...rprops}
182207
onChange={onChange}
183208
onKeyDown={onKeyDown}
209+
aria-labelledby={labelledBy}
210+
aria-describedby={error ? errorId : undefined}
211+
aria-invalid={error ? true : undefined}
184212
/>
185213
);
186214

@@ -196,20 +224,26 @@ export const Input = createFC<InputProps, { isFormElement: boolean }>(
196224
);
197225
contentElem = (
198226
<div className={wrapperClassName}>
199-
{addonLeft ? <InputAddon content={addonLeft} /> : undefined}
227+
{addonLeft ? (
228+
<InputAddon content={addonLeft} id={preAddonId} />
229+
) : undefined}
200230
{iconLeft ? <InputIcon icon={iconLeft} align='left' /> : undefined}
201231
{inputElem}
202232
{iconRight ? <InputIcon icon={iconRight} align='right' /> : undefined}
203-
{addonRight ? <InputAddon content={addonRight} /> : undefined}
233+
{addonRight ? (
234+
<InputAddon content={addonRight} id={postAddonId} />
235+
) : undefined}
204236
</div>
205237
);
206238
}
207239
if (isFieldSetColumn || label || required || error || cols) {
208240
const formElemProps = {
209-
id,
241+
id: labelId,
242+
controlId: labelFor,
210243
label,
211244
required,
212245
error,
246+
errorId,
213247
readOnly,
214248
cols,
215249
tooltip,

0 commit comments

Comments
 (0)