XDSTextArea@xds/core · TextArea
Usage
TextArea is a multi-line text input for collecting longer-form content like comments, descriptions, or messages. Use it when the expected input spans multiple lines. For shorter, single-line values, use TextInput.Best practices
| Guidance | Practices |
|---|---|
| Do | Provide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers. |
| Do | Set maxLength with a character counter when there is a defined limit — it helps users stay within bounds before they submit. |
| Do | Use the status prop to surface validation feedback inline — show success when input is valid, warning for soft limits, and error for hard failures. |
| Do | Add a description or placeholder to clarify expected content, like "Describe the issue in detail" — but never rely on placeholder alone as the only label. |
| Don't | Avoid using TextArea for short, single-line values like names or emails — use TextInput instead. |
| Don't | Don't rely solely on placeholder text to communicate the purpose of the field — placeholders disappear on focus and are not accessible labels. |
| Don't | Don't show a status message without also setting the status type — the colored border and icon are what draw the user's attention to the message. |
Import
tsimport {XDSTextArea} from '@xds/core/TextArea'
Props
| Prop | Type | Description |
|---|---|---|
labelrequired | string | Label text for the textarea — always rendered for accessibility. |
valuerequired | string | Current value of the textarea. |
ref | React.Ref<HTMLTextAreaElement> | Ref forwarded to the underlying <textarea> element. |
onChange | (value: string, e: ChangeEvent<HTMLTextAreaElement>) => void | Callback fired when the textarea value changes. |
changeAction | (value: string, e: ChangeEvent<HTMLTextAreaElement>) => void | Promise<void> | Async action fired after onChange inside a React transition. Enables optimistic updates via useOptimistic. |
isLabelHidden | boolean (default: false) | Visually hides the label while keeping it accessible to screen readers. |
description | string | Helper text displayed between the label and textarea. |
isOptional | boolean (default: false) | Displays an "Optional" indicator next to the label. Mutually exclusive with isRequired. |
isRequired | boolean (default: false) | Displays a "Required" indicator next to the label and sets aria-required. Mutually exclusive with isOptional. |
isDisabled | boolean (default: false) | Disables the textarea, preventing interaction. |
isLoading | boolean (default: false) | Puts the textarea in a loading state, showing a spinner inside the input. |
placeholder | string | Placeholder text shown when the textarea is empty. |
rows | number (default: 3) | Number of visible text rows. |
maxLength | number | Maximum number of characters allowed. When set, a character counter (current/max) is displayed below the textarea. Does not enforce the limit natively — the counter shows error styling when exceeded. |
status | { type: 'warning' | 'error' | 'success'; message?: string } | Status indicator that applies a colored border and icon. An optional message is displayed in a floating box below the textarea. |
labelTooltip | string | Tooltip text displayed in an info icon at the end of the label. |
startIcon | XDSIconType | Icon component rendered inside the leading edge of the textarea wrapper. See `npx xds docs icons` for valid semantic names. |
hasSpellCheck | boolean (default: true) | Enables or disables browser spell checking. |
hasAutoFocus | boolean (default: false) | Automatically focuses the textarea on mount. |
onPaste | (e: ClipboardEvent<HTMLTextAreaElement>) => void | Callback fired when content is pasted into the textarea. |
htmlName | string | HTML name attribute for the textarea element, useful for form submissions. |
onFocus | (e: FocusEvent<HTMLTextAreaElement>) => void | Callback fired when the textarea receives focus. |
onBlur | (e: FocusEvent<HTMLTextAreaElement>) => void | Callback fired when the textarea loses focus. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}. |
Examples
Common configurations, variations, and states.TextArea — Character CountTextareas with maxLength and a live character counter. The counter turns red when the limit is exceeded.
tsx'use client';import {useState} from 'react';import {XDSTextArea} from '@xds/core/TextArea';export default function TextAreaCharacterCount() {const [value, setValue] = useState('Excited to announce that our team just shipped the new dashboard! Check it out and let us know what you think.',);return (<div style={{width: 400}}><XDSTextArealabel="Status update"value={value}onChange={setValue}placeholder="What's on your mind?"maxLength={280}rows={3}/></div>);}
TextArea — IconTextareas with a leading icon that hints at the expected content, like a chat bubble for messages or a pencil for notes.
tsx'use client';import {useState} from 'react';import {XDSTextArea} from '@xds/core/TextArea';import {PencilSquareIcon} from '@heroicons/react/24/outline';export default function TextAreaWithIcon() {const [value, setValue] = useState('');return (<div style={{width: 400}}><XDSTextArealabel="Meeting notes"description="Capture key decisions and action items."value={value}onChange={setValue}placeholder="What was discussed?"startIcon={PencilSquareIcon}/></div>);}
TextArea — StatesRequired, disabled, and loading textareas side by side. Shows the interactive states the component supports.
tsx'use client';import {useState} from 'react';import {XDSTextArea} from '@xds/core/TextArea';import {XDSStack} from '@xds/core/Layout';export default function TextAreaStates() {const [requiredValue, setRequiredValue] = useState('');return (<XDSStack direction="vertical" gap={4} style={{width: 400}}><XDSTextArealabel="Required field"value={requiredValue}onChange={setRequiredValue}placeholder="Describe the issue..."isRequired/><XDSTextArealabel="Disabled field"value="This field is read-only and cannot be edited."onChange={() => {}}isDisabled/><XDSTextArealabel="Loading field"value=""onChange={() => {}}placeholder="Generating summary..."isLoading/></XDSStack>);}
TextArea — ValidationAll three status variants — error, warning, and success — with status messages, plus error without a message. Use to show inline validation feedback as the user types.
tsx'use client';import {useState} from 'react';import {XDSTextArea} from '@xds/core/TextArea';import {XDSStack} from '@xds/core/Layout';export default function TextAreaValidation() {const [errorValue, setErrorValue] = useState('Fix the');const [warningValue, setWarningValue] = useState('Summarize the Q2 results');const [successValue, setSuccessValue] = useState('Redesign the onboarding flow to reduce drop-off by 15% in Q3. Focus on simplifying the account creation step and adding a progress indicator.',);const [errorNoMsgValue, setErrorNoMsgValue] = useState('Invalid content');return (<XDSStack direction="vertical" gap={4} style={{width: 400}}><XDSTextArealabel="Error message"value={errorValue}onChange={setErrorValue}status={{type: 'error',message: 'Description must be at least 20 characters.',}}/><XDSTextArealabel="Warning message"value={warningValue}onChange={setWarningValue}status={{type: 'warning',message: 'Consider adding more detail for clarity.',}}/><XDSTextArealabel="Success message"value={successValue}onChange={setSuccessValue}status={{type: 'success',message: 'Looks good — clear and actionable.',}}/><XDSTextArealabel="Error without message"value={errorNoMsgValue}onChange={setErrorNoMsgValue}status={{type: 'error'}}/></XDSStack>);}
Showcase source
tsx'use client';import {XDSTextArea} from '@xds/core/TextArea';export default function TextAreaShowcase() {return (<div style={{width: 400}}><XDSTextArealabel="Description"value=""onChange={() => {}}placeholder="Enter a description..."/></div>);}