Input
A fully accessible text input component with built-in label, helper text, error handling, size variants, and WCAG-compliant focus behavior.
Usage
Basic usage
import { Input } from '@nofinite/nui';
<Input label="Email" placeholder="email@example.com" />;
With validation and helper text
<Input
label="Password"
type="password"
error="Must be at least 8 characters"
/>
<Input
label="Name"
required
description="This will appear on your profile."
/>
Variants
Input does not have visual style variants (like primary/secondary).
Instead, it focuses on state-based styling (error, disabled) and size variants.
Sizes
The input supports three size variants via the inputSize prop.
<Input inputSize="sm" label="Small input" />
<Input inputSize="md" label="Medium input" />
<Input inputSize="lg" label="Large input" />
Available sizes:
sm– Compact inputs for dense layoutsmd(default) – Standard form usagelg– Emphasized or primary form fields
States
Required
<Input label="Email" required />
- Automatically appends
*to the label. - Uses native
requiredbehavior.
Disabled
<Input label="Username" disabled />
- Reduced opacity
- Cursor disabled
- Non-interactive
Error
<Input label="Password" type="password" error="Must be at least 8 characters" />
- Red border and background
- Error message announced via
aria-describedby aria-invalid="true"applied automatically
Native Props / Composition
Input extends all native <input> props via
React.InputHTMLAttributes<HTMLInputElement>.
<Input
label="Search"
placeholder="Search..."
onChange={handleChange}
aria-label="Search input"
data-testid="search-input"
/>
Custom wrapper class
<Input label="Email" className="my-custom-wrapper" />
Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Label displayed above the input |
description | string | — | Helper text shown below the input (hidden when error exists) |
error | string | — | Error message and error styling |
inputSize | "sm" | "md" | "lg" | md | Controls padding and font size |
id | string | auto | Overrides auto-generated ID |
className | string | "" | Custom wrapper class |
...rest | InputHTMLAttributes<HTMLInputElement> | — | All native input attributes |
Behavior Notes
-
Automatically generates a stable ID using
React.useId() -
If
idis passed, it overrides the auto-generated ID -
aria-describedbydynamically switches between:- error message
- helper description
-
Error message always takes priority over description
Example:
<Input
label="Username"
description="Publicly visible name"
error="Username already taken"
/>
Accessibility
-
Renders a native
<input>with an associated<label> -
Uses:
aria-invalidfor error statearia-describedbyfor error/description
-
WCAG-compliant contrast and focus indicators
-
:focus-visibleused to avoid mouse focus noise -
Fully keyboard accessible (Tab, Shift+Tab)
Layout
- Block-level component
- Always takes full width of its container
- Wrapper uses column layout with consistent spacing
<div style={{ width: 250 }}>
<Input label="Email" />
</div>
Best Practices
Do
- Always provide a
labelfor accessibility - Use
descriptionfor guidance, not validation - Use
erroronly for actionable feedback - Let the input stretch to container width
Don’t
- Use placeholder text as a label replacement
- Show both
descriptionanderrorat the same time - Disable inputs without explaining why
- Overuse
lgsize in dense forms