HelperText
The little message under an input — a hint while you type, the verdict once a check runs. Four severity tones match Input's states so the field and its message always agree: neutral / valid / caution / invalid. Per ADR-0017, severity-toned helper text always pairs with an icon — color alone is never the signal.
For the canonical form-field composition (label + input + helper text with auto-linked ARIA via aria-describedby), use /text-field. Use HelperText on its own for one-off hints that don’t need a label association.
Variants
Four severity tones. Pass the icon as the first child — the pattern is flex and the icon aligns to the first line of text.
We’ll never share your email with anyone.
Username available.
This action can’t be undone.
Email format invalid — must include an @ sign.
Paired with an Input
The whole point — HelperText sits below an Input and tells the user what’s happening with the value. Severity tones on HelperText pair with the matching state on Input so the field and its message read as one validated unit.
We’ll never share your email.
Email format invalid — must include an @ sign.
Username available.
Transferring ownership is irreversible.
Password match — live demo
The canonical use of the valid / invalid pair on Input and HelperText together. Edit either field and watch the message and border switch live.
At least 12 characters.
Passwords match.
Props
- variant
'neutral' | 'valid' | 'caution' | 'invalid'Severity tone. Defaults to
neutral. Severity-toned variants (valid, caution, invalid) require an icon child per ADR-0017.- children
ReactNodeThe message text, optionally preceded by an icon. The pattern is flex; pass the icon as the first child to get baseline-aligned icon + text.
- …native attrs
HTMLAttributes<HTMLParagraphElement>All standard
<p>attributes pass through.idis useful when wiringaria-describedbyfrom a paired input — TextField does this automatically.
Usage
import { HelperText } from '@halo-compliance/ui'
<HelperText>
We'll never share your email.
</HelperText><HelperText variant="invalid">
<CircleX /> Email format invalid.
</HelperText><Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
invalid={!!error}
aria-describedby="email-helper"
/>
<HelperText
id="email-helper"
variant={error ? 'invalid' : 'neutral'}
>
{error ? <><CircleX /> {error}</> : "We'll never share it."}
</HelperText>Anti-patterns
- Don’t carry severity by color alone. Every non-neutral variant requires an icon (ADR-0017). The icon is the unambiguous signal for color-blind users.
- Don’t use HelperText for body content. It’s specifically a field-message text role — small, baseline-aligned, paired with an affordance. For inline prose use <Text variant="body-sm">.
- Don’t put the icon inside a wrapper. The component’s flex layout aligns the icon to the first line of text. Wrapping the icon in a span breaks the alignment.
- Don’t pair a valid HelperText with an idle field. The whole “validation passed” signal is the field+message together — if you can’t put the field in valid state, don’t put the message in valid tone.
Related
- /input — the field HelperText sits below. The severity tones here mirror Input’s valid / invalid states.
- /text-field — the compound pattern that wraps Input + HelperText with auto-linked ARIA. Reach for that when you need the full form-field shape.
- /tokens —
--patina-700,--caution-700,--rust-700for the severity text colors.