XDSTextArea@xds/core · TextArea
Preview coming soon

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

GuidancePractices
DoProvide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers.
DoSet maxLength with a character counter when there is a defined limit — it helps users stay within bounds before they submit.
DoUse the status prop to surface validation feedback inline — show success when input is valid, warning for soft limits, and error for hard failures.
DoAdd 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'tAvoid using TextArea for short, single-line values like names or emails — use TextInput instead.
Don'tDon't rely solely on placeholder text to communicate the purpose of the field — placeholders disappear on focus and are not accessible labels.
Don'tDon'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

ts
import {XDSTextArea} from '@xds/core/TextArea'

Props

PropTypeDescription
labelrequired
stringLabel text for the textarea — always rendered for accessibility.
valuerequired
stringCurrent value of the textarea.
ref
React.Ref<HTMLTextAreaElement>Ref forwarded to the underlying <textarea> element.
onChange
(value: string, e: ChangeEvent<HTMLTextAreaElement>) => voidCallback 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
stringHelper 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
stringPlaceholder text shown when the textarea is empty.
rows
number (default: 3)Number of visible text rows.
maxLength
numberMaximum 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
stringTooltip text displayed in an info icon at the end of the label.
startIcon
XDSIconTypeIcon 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>) => voidCallback fired when content is pasted into the textarea.
htmlName
stringHTML name attribute for the textarea element, useful for form submissions.
onFocus
(e: FocusEvent<HTMLTextAreaElement>) => voidCallback fired when the textarea receives focus.
onBlur
(e: FocusEvent<HTMLTextAreaElement>) => voidCallback fired when the textarea loses focus.
xstyle
StyleXStylesStyleX 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}}>
<XDSTextArea
label="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}}>
<XDSTextArea
label="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}}>
<XDSTextArea
label="Required field"
value={requiredValue}
onChange={setRequiredValue}
placeholder="Describe the issue..."
isRequired
/>
<XDSTextArea
label="Disabled field"
value="This field is read-only and cannot be edited."
onChange={() => {}}
isDisabled
/>
<XDSTextArea
label="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}}>
<XDSTextArea
label="Error message"
value={errorValue}
onChange={setErrorValue}
status={{
type: 'error',
message: 'Description must be at least 20 characters.',
}}
/>
<XDSTextArea
label="Warning message"
value={warningValue}
onChange={setWarningValue}
status={{
type: 'warning',
message: 'Consider adding more detail for clarity.',
}}
/>
<XDSTextArea
label="Success message"
value={successValue}
onChange={setSuccessValue}
status={{
type: 'success',
message: 'Looks good — clear and actionable.',
}}
/>
<XDSTextArea
label="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}}>
<XDSTextArea
label="Description"
value=""
onChange={() => {}}
placeholder="Enter a description..."
/>
</div>
);
}