Live code
Storybook
Usage
TextArea
TextArea enables the user to add longer text to a form. It could be disabled or have a validation state with optional validation text. The label could be hidden and show if the textarea is required.
Basic example usage:
<TextArea id="text-area-default" label="Label" name="textAreaDefault" />
Advanced example usage:
<TextArea
hasValidationIcon
helperText="custom helper text"
id="text-area-advanced"
isRequired
label="Label"
maxlength="180"
name="textAreaAdvanced"
placeholder="Placeholder"
rows="10"
size="large"
validationState="danger"
validationText="validation failed"
value="TextArea"
/>
Example with Auto-Height Adjustment
<TextArea id="example" label="Label" name="example" isAutoResizing autoResizingMaxHeight={500} />
Counter
The counter displays the current character count. It can be shown in two modes:
- With threshold (counterThreshold): Shows current/threshold (e.g. 5/200). Setting counterThreshold automatically enables the counter.
- Count only (hasCounter): Shows just the current count (e.g. 5).
The counter itself is purely informational — it does not set any validation state or validation text. If you need to show an error when the limit is exceeded, control validationState and validationText yourself.
Native maxLength (counter On)
When the counter is shown (counterThreshold and/or hasCounter), Spirit applies HTML maxLength as a safety net (very long input can hurt browser performance).
- If you do not pass maxLength, the textarea uses the library's internal upper bound (10000).
- If you do pass maxLength, your value is used directly.
- Default behavior we want: keep maxLength open (or high) and handle over-limit UI via counterThreshold + your own validation state/text.
- If you set maxLength to the same value as counterThreshold (e.g. both 200), native hard cap applies: users cannot paste/type over the limit and over-limit validation state will not be reached.
Without the counter, Spirit does not add maxLength; you can still set it yourself via the normal textarea attribute.
Counter with threshold (default, soft-limit behavior):
<TextArea id="text-area-counter" label="Label" counterThreshold={200} />
Counter showing only the count:
<TextArea id="text-area-counter-only" label="Label" hasCounter />
Counter with minimum length hint — use helperText to communicate the expected range:
<TextArea id="text-area-range" label="Label" counterThreshold={200} helperText="Write between 100 and 200 characters" />
or just a minimum:
<TextArea id="text-area-min" label="Label" counterThreshold={500} helperText="Write at least 100 characters" />
Counter with threshold and hard cap (less preferred, use only when you want strict native blocking):
<TextArea id="text-area-counter-hard-cap" label="Label" counterThreshold={200} maxLength={200} />
Counter Accessibility
The counter uses a two-element pattern so that sighted users and screen reader users each get an optimized experience:
- Visible counter (.TextArea__counter): Displays the compact current/max or count-only format with aria-hidden="true" so screen readers skip it.
- Screen reader message (VisuallyHidden): A human-readable, debounced message (e.g. "195 characters remaining" or "5 characters entered") linked to the textarea via aria-describedby and announced through aria-live="polite".
API
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| autoComplete | string | - | ✕ | Automated assistance in filling |
| autoResizingMaxHeight | number | 400 | ✕ | Maximum field height with automatic height control |
| counterThreshold | number | — | ✕ | Character threshold; shows current/threshold counter |
| hasCounter | bool | — | ✕ | Show character counter (count only); auto true with counterThreshold |
| maxLength | number | — | ✕ | Native textarea hard cap; with counterThreshold, prefer >= counterThreshold |
| hasValidationIcon | bool | false | ✕ | Whether to show validation icon |
| helperText | string | — | ✕ | Custom helper text |
| id | string | — | ✓ | Textarea and label identification |
| isAutoResizing | bool | — | ✕ | Whether is field auto resizing which adjusts its height while typing |
| isDisabled | bool | — | ✕ | Whether is field disabled |
| isLabelHidden | bool | — | ✕ | Whether is label hidden |
| isRequired | bool | — | ✕ | Whether is field required |
| label | ReactNode | — | ✓ | Label text |
| name | string | — | ✕ | Textarea name |
| placeholder | string | — | ✕ | Textarea placeholder |
| ref | ForwardedRef<HTMLTextAreaElement> | — | ✕ | Textarea element reference |
| rows | number | — | ✕ | Number of visible rows |
| size | Size dictionary | medium | ✕ | Size variant |
| validationState | Validation dictionary | — | ✕ | Type of validation state |
| validationText | [ReactNode | ReactNode[]] | — | ✕ | Validation text |
| value | string | — | ✕ | Textarea value |
On top of the API options, the components accept additional attributes. If you need more control over the styling of a component, you can use style props and escape hatches.
Custom Component
Text field classes are fabricated using useTextAreaStyleProps hook. You can use it to create your own custom TextArea component.
const CustomTextArea = (props: SpiritTextAreaProps): JSX.Element => {
const { classProps, props: modifiedProps } = useTextAreaStyleProps(props);
return (
<div className={classProps.root}>
<textarea {...modifiedProps} className={classProps.input}></textarea>
<label htmlFor={props.id} className={styleProps.label}>
{props.label}
</label>
</div>
);
};
For detailed information see TextArea component.