Skip to content

Commit 9d2e21e

Browse files
feat(ui): improve Input component accessibility
- Auto-generate unique IDs using `useId` - Associate label with input via `htmlFor` - Add ARIA attributes for error state (`aria-invalid`, `aria-describedby`) - Add `role="alert"` to error message
1 parent a035ec0 commit 9d2e21e

File tree

1 file changed

+29
-5
lines changed

1 file changed

+29
-5
lines changed

web/components/ui/Input.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useId } from 'react';
22
import { useTheme } from '../../contexts/ThemeContext';
33
import { THEMES } from '../../constants';
44

@@ -7,8 +7,11 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
77
error?: string;
88
}
99

10-
export const Input: React.FC<InputProps> = ({ label, error, className = '', ...props }) => {
10+
export const Input: React.FC<InputProps> = ({ label, error, className = '', id, ...props }) => {
1111
const { style, mode } = useTheme();
12+
const generatedId = useId();
13+
const inputId = id || generatedId;
14+
const errorId = `${inputId}-error`;
1215

1316
let inputStyles = "w-full outline-none transition-all duration-200";
1417

@@ -20,9 +23,30 @@ export const Input: React.FC<InputProps> = ({ label, error, className = '', ...p
2023

2124
return (
2225
<div className="flex flex-col gap-1 w-full">
23-
{label && <label className={`text-sm font-semibold ${style === THEMES.NEOBRUTALISM ? 'uppercase' : 'ml-1 opacity-80'}`}>{label}</label>}
24-
<input className={`${inputStyles} ${className}`} {...props} />
25-
{error && <span className="text-red-500 text-xs font-bold mt-1">{error}</span>}
26+
{label && (
27+
<label
28+
htmlFor={inputId}
29+
className={`text-sm font-semibold ${style === THEMES.NEOBRUTALISM ? 'uppercase' : 'ml-1 opacity-80'}`}
30+
>
31+
{label}
32+
</label>
33+
)}
34+
<input
35+
id={inputId}
36+
className={`${inputStyles} ${className}`}
37+
aria-invalid={!!error}
38+
aria-describedby={error ? errorId : undefined}
39+
{...props}
40+
/>
41+
{error && (
42+
<span
43+
id={errorId}
44+
className="text-red-500 text-xs font-bold mt-1"
45+
role="alert"
46+
>
47+
{error}
48+
</span>
49+
)}
2650
</div>
2751
);
2852
};

0 commit comments

Comments
 (0)