At least 8 characters.
import {
HelperText,
Label,
TextField,
TextFieldInput,
} from '@vega-ui/react';
<div>
<Label htmlFor='preview-helper-password'>Password</Label>
<TextField>
<TextFieldInput id='preview-helper-password' type='password' defaultValue='secret' aria-describedby='preview-helper-hint' />
</TextField>- Browser support
- Widely available
- Chrome 111
- Edge 111
- Firefox 113
- Safari 15.4
- Safari on iOS 15.4
- Bundle size
- Oklab and OkLCh — comes from the design tokens
- :where()
Summary
HelperText renders supporting field copy such as hints, usage guidance, and validation-adjacent messages. It reuses the Text typography layer and adds semantic helper or error coloring for field-level support content.
Imports
import { HelperText, type HelperTextProps } from '@vega-ui/react';Exported Types
HelperTextProps
Basic Usage
import { HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldHint() {
const id = useId();
const hintId = `${id}-hint`;
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Recovery email</Label>
<TextField>
<TextFieldInput id={id} type='email' autoComplete='email' aria-describedby={hintId} />Variants
- Size:
sm,md,lg - State: default helper copy or
error - Placement: below fields, selectors, or grouped controls
Edge Cases
errorchanges tone, but the field itself still needs its own invalid state.- Helper copy should stay short enough to scan with the associated control.
- Long explanatory text usually belongs in body copy, not helper text.
- The component supports one control or one grouped field concept; it should not become a dumping ground for unrelated form instructions.
Common Mistakes
- Repeating the same content already present in the label.
- Using helper text as the only error indicator.
- Writing policy or legal paragraphs in helper text style.
Examples
Basic
Basic: hint linked to a field
Give HelperText an id and reference it from the input's aria-describedby; otherwise screen readers never connect the hint to the field. The hint is read after the label when the input gets focus.
import { HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldHint() {
const id = useId();
const hintId = `${id}-hint`;
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Recovery email</Label>
<TextField>
<TextFieldInput id={id} type='email' autoComplete='email' aria-describedby={hintId} />Controlled/Stateful
Controlled/Stateful: remaining characters
The count is derived from the value on each keystroke and turns into an error tone at the limit. Announcing every keystroke would be noisy, so a separate hidden status region speaks only in the last ten characters.
import { HelperText, Label, TextField, TextFieldInput, VisuallyHidden } from '@vega-ui/react';
import { useId, useState } from 'react';
const MAX_LENGTH = 50;
export default function RemainingCharacters() {
const id = useId();
const [subject, setSubject] = useState('Your March invoice is ready');
const remaining = MAX_LENGTH - subject.length;
const message = remaining === 1 ? '1 character left' : `${remaining} characters left`;
return (Form/Integration
Form/Integration: hint for a group of controls
One hint for several checkboxes belongs to the group: the Fieldset references it with aria-describedby. On submit, FormData.getAll collects every checked channel.
import {
Button,
Checkbox,
CheckboxCheckedIcon,
CheckboxIndicator,
CheckboxInput,
Fieldset,
FieldsetLegend,
HelperText,
Text,
} from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';Layout/Overlay
Layout/Overlay: reserved space for the message
The message slot keeps a minHeight of two lines, so an appearing error does not push the next row down or misalign fields that sit side by side.
import { HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId, useState } from 'react';
export default function ReservedMessageSpace() {
const id = useId();
const [code, setCode] = useState('');
const invalid = code !== '' && !/^[A-Z]{3}-\d{4}$/.test(code);
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 'var(--spacing-8)', width: '100%' }}>
<div style={{ display: 'grid', gap: 'var(--spacing-3)', alignContent: 'start' }}>
<Label htmlFor={`${id}-code`}>Booking code</Label>Error
Error: message from native validity
onInvalid turns the browser's validity flags into a specific message and suppresses the native bubble. The error is listed first in aria-describedby, before the permanent hint, and it is re-checked on input until it clears.
import { Button, HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { FormEvent, useId, useState } from 'react';
const getMessage = (input: HTMLInputElement) => {
if (input.validity.valueMissing) return 'Enter a postal code.';
if (input.validity.patternMismatch) return 'Use 5 digits, for example 10115.';
return '';
};
export default function NativeValidityMessage() {
const id = useId();
const [error, setError] = useState('');Disabled
Disabled: explain why a field is locked
A disabled input cannot be focused, so the reason must be visible text rather than a tooltip. The hint says why the field is locked and links to the way out.
import { HelperText, Label, Link, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function ExplainDisabledField() {
const id = useId();
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id} style={{ color: 'var(--label-tertiary)' }}>Custom domain</Label>
<TextField>
<TextFieldInput id={id} placeholder='docs.northwind.io' disabled aria-describedby={`${id}-hint`} />
</TextField>Edge
Edge: long unbroken text in a narrow column
HelperText is inline-block, so a long URL would overflow a narrow column. overflowWrap: 'anywhere' lets it break at any character.
import { HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function LongUnbrokenHint() {
const id = useId();
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%', maxWidth: 240 }}>
<Label htmlFor={id}>Webhook URL</Label>
<TextField>
<TextFieldInput id={id} type='url' aria-describedby={`${id}-hint`} />
</TextField>Anatomy
Overview
HelperText is a single-part primitive. It renders a styled paragraph through the shared Text contract.
Required Parts
HelperText
Required. Owns helper-copy sizing and muted or error color.
Optional Parts
There are no exported child parts.
Composition Order
HelperText- short supporting copy
Valid Composition Patterns
import { HelperText, Label, TextField, TextFieldInput } from '@vega-ui/react';
import { useId } from 'react';
export default function FieldHint() {
const id = useId();
const hintId = `${id}-hint`;
return (
<div style={{ display: 'grid', gap: 'var(--spacing-3)', width: '100%' }}>
<Label htmlFor={id}>Recovery email</Label>
<TextField>
<TextFieldInput id={id} type='email' autoComplete='email' aria-describedby={hintId} />Invalid Composition Patterns
Helper text used as the primary label
This weakens visible labeling and semantics.
Helper text used for full policy copy
The component is too lightweight for long-form explanatory content.