@xsolla/xui-textarea
v0.216.1
Published
A cross-platform React textarea component for multi-line text input. Includes error and warning states and validation message support. <!-- BEGIN:xui-mcp-instructions:textarea --> A multi-line text input for collecting longer freeform content. Supports fi
Downloads
16,264
Readme
Textarea
A cross-platform React textarea component for multi-line text input. Includes error and warning states and validation message support.
A multi-line text input for collecting longer freeform content. Supports five sizes, an optional scroll bar, an optional resize handle, placeholder text, and the full standard input state set. Used wherever the expected input length exceeds a single line — comments, descriptions, messages, notes, and code snippets.
When to use
When the user needs to enter multiple sentences, paragraphs, or structured freeform content — descriptions, comments, feedback, notes, bios, addresses
When the expected input length is unpredictable or variable and a fixed single-line field would feel too restrictive
In forms where a content field needs to stand visually apart from short text inputs by its height
For developer or admin contexts where code, JSON, or multi-line configuration is entered
When not to use
- For short, single-value inputs (name, email, URL) — use a standard Input
- When the user must select from predefined options — use Select or ContextMenu
- When rich formatting (bold, links, lists) is needed — use a rich-text editor, not TextArea
- For search queries or single-line filter inputs — use Input with appropriate type
Content guidelines
- Labels should be short, specific, and written in sentence case: "Description", "Additional notes", "Message", not "DESCRIPTION".
- Use labels that clearly describe the expected content: "Project summary", "Delivery instructions", "Tell us more", not vague labels like "Text" or "Write here".
- Helper text should clarify purpose, expected level of detail, or constraints when needed: "Include key goals and timelines", "Maximum 500 characters".
- Placeholder text should support the label, not replace it. Use short examples or prompts only when they reduce ambiguity, and avoid placing essential instructions in the placeholder because it disappears as the user types.
- Error messages should be explicit and actionable: "Enter a description", "Message must be 500 characters or fewer" — not generic messages like "Invalid input".
- Keep labels concise. If the field needs more explanation, add helper text nearby — not in the label or placeholder.
Behaviour guidelines (from industry practice)
Auto-grow — for most form contexts, prefer auto-grow behaviour: the field starts at the Size height and expands vertically as the user types, rather than scrolling. Pair this with Resize handle=false and Scroll bar=false. Implement via JavaScript (scrollHeight technique) or a CSS field-sizing: content property.
Fixed height with scroll — for contexts where layout stability matters (e.g. a message composer in a chat UI with a fixed panel), use a fixed height and enable Scroll bar=true so the user can scroll the content within the field.
Character count — if the field has a maximum length, display a character counter below the field (e.g. 240 / 500). Update the counter in real time as the user types. When the limit is reached, prevent further input and switch to State=Error with a descriptive message.
Validation — validate on blur (when the user leaves the field) or on form submit. Do not show errors while the user is actively typing. Switch to State=Error with a specific error message when the value fails validation.
Paste — always allow paste. Do not block Ctrl+V / Cmd+V. If content from paste exceeds the character limit, truncate to the limit and show the character counter at maximum.
Tab key — in most form contexts, Tab should move focus to the next field. In code editors or developer-facing TextAreas where tab indentation is expected, handle Tab to insert a tab character and provide a clear way to exit the field (e.g. Escape followed by Tab, or a documented keyboard shortcut).
Disabled state — the field is not focusable and not editable. If a value is present, it remains visible in muted style. Provide an explanation (tooltip or nearby label) if the reason for disabling is not obvious.
Placeholder disappears — placeholder text is shown only in the empty state. It is not a label and must not be relied upon as the sole description of the field.
Accessibility
Use a native element — it inherits keyboard focus, selection, and browser spellcheck behaviour for free.
Always provide a visible label associated via or aria-labelledby. Do not use placeholder as the only label — it disappears when the user starts typing and is not reliably announced by all screen readers.
When State=Error, the error message must be linked via aria-describedby so screen readers announce it when the field receives focus.
When State=Disable, the element must have the disabled attribute. Disabled fields are not focusable by keyboard.
If a character counter is shown, it must be in an aria-live="polite" region so screen readers announce the count as it updates. Link it to the field via aria-describedby.
If Tab is intercepted to insert indentation (developer use case), provide an accessible escape mechanism and document it: include an aria-description on the field noting how to exit — e.g. "Press Escape then Tab to move to the next field".
The resize handle must be keyboard-operable if implemented. Consider providing ↑ / ↓ arrow key controls to adjust the height when the handle has focus, with aria-label="Resize text field".
Installation
npm install @xsolla/xui-textareaValidation states
| State | Prop | Meaning |
| :-------- | :------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| Error | errorMessage (or state="error") | Blocking. The value is invalid and must be fixed before the form can be submitted. |
| Warning | warningMessage (or state="warning") | Non-blocking. The value is allowed but needs attention — e.g. an empty optional field the user probably meant to fill in. |
Passing errorMessage or warningMessage implies the matching state, so state
is only needed when you want the border highlight without a message below the
field — for forms that render validation text somewhere else (a form-level error
summary, a shared label, a tooltip).
Error takes precedence over warning: when both are supplied, the field renders as an error and the warning is suppressed.
Demo
Basic Textarea
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function BasicTextarea() {
const [value, setValue] = React.useState("");
return (
<TextArea
value={value}
onChangeText={setValue}
placeholder="Enter your message..."
/>
);
}Textarea Sizes
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaSizes() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<TextArea size="xs" placeholder="Extra Small" />
<TextArea size="sm" placeholder="Small" />
<TextArea size="md" placeholder="Medium (default)" />
<TextArea size="lg" placeholder="Large" />
<TextArea size="xl" placeholder="Extra Large" />
</div>
);
}Textarea with Error
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaWithError() {
const [value, setValue] = React.useState("");
const maxLength = 100;
return (
<TextArea
value={value}
onChangeText={setValue}
placeholder="Enter description..."
errorMessage={
value.length > maxLength
? `Maximum ${maxLength} characters allowed`
: ""
}
/>
);
}Textarea with Warning
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaWithWarning() {
const [value, setValue] = React.useState("");
return (
<TextArea
value={value}
onChangeText={setValue}
placeholder="Description (optional)"
warningMessage={
value.trim() === ""
? "Adding a description helps reviewers understand your request"
: undefined
}
/>
);
}Border Highlight Without a Message
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaBorderHighlight() {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
{/* The validation text lives in a form-level summary elsewhere */}
<TextArea state="warning" placeholder="Needs attention" />
<TextArea state="error" placeholder="Invalid" />
</div>
);
}Textarea with Blur Validation
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaBlurValidation() {
const [value, setValue] = React.useState("");
const [error, setError] = React.useState<string | undefined>(undefined);
return (
<TextArea
value={value}
onChangeText={setValue}
onBlur={() =>
setError(value.trim() === "" ? "This field is required" : undefined)
}
onFocus={() => setError(undefined)}
errorMessage={error}
placeholder="Required field"
/>
);
}Anatomy
Import the component and use it directly:
import { TextArea } from "@xsolla/xui-textarea";
<TextArea
value={text} // Controlled value
onChangeText={setText} // Value change handler
placeholder="Placeholder" // Placeholder text
size="md" // Size variant
disabled={false} // Disabled state
state="default" // Border highlight without a message
errorMessage="Error text" // Error message below textarea
warningMessage="Warning text" // Warning message below textarea
/>;Examples
Comment Form
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
import { Button } from "@xsolla/xui-button";
export default function CommentForm() {
const [comment, setComment] = React.useState("");
const handleSubmit = () => {
console.log("Comment:", comment);
setComment("");
};
return (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<TextArea
value={comment}
onChangeText={setComment}
placeholder="Write a comment..."
size="md"
/>
<Button onPress={handleSubmit} disabled={!comment.trim()}>
Post Comment
</Button>
</div>
);
}Disabled Textarea
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function DisabledTextarea() {
return (
<TextArea
value="This content cannot be edited"
disabled
placeholder="Disabled textarea"
/>
);
}Soft Validation on Blur
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaSoftValidation() {
const [value, setValue] = React.useState("");
const [warning, setWarning] = React.useState<string | undefined>(undefined);
// An empty value is permitted, so the form can still be submitted — the
// field simply nudges the user instead of reporting an error.
return (
<TextArea
value={value}
onChangeText={setValue}
onBlur={() =>
setWarning(
value.trim() === ""
? "Adding a description helps reviewers understand your request"
: undefined
)
}
onFocus={() => setWarning(undefined)}
warningMessage={warning}
placeholder="Description (optional)"
/>
);
}Character Counter
import * as React from "react";
import { TextArea } from "@xsolla/xui-textarea";
export default function TextareaWithCounter() {
const [value, setValue] = React.useState("");
const maxLength = 500;
return (
<div>
<TextArea
value={value}
onChangeText={setValue}
placeholder="Enter your bio..."
errorMessage={
value.length > maxLength ? "Character limit exceeded" : ""
}
/>
<div
style={{
textAlign: "right",
fontSize: 12,
color: value.length > maxLength ? "red" : "gray",
}}
>
{value.length}/{maxLength}
</div>
</div>
);
}API Reference
TextArea
A multi-line text input component.
TextArea Props:
| Prop | Type | Default | Description |
| :--------------- | :--------------------------------------- | :------------ | :-------------------------------------------------------------------------------------------------------------- |
| testID | string | — | Test ID for testing frameworks. On web this renders as data-testid; on React Native it renders as testID. |
| value | string | - | The controlled value of the textarea. |
| placeholder | string | - | Placeholder text when empty. |
| onChangeText | (text: string) => void | - | Callback when text changes. |
| onBlur | () => void | - | Callback fired when the textarea loses focus. Ideal for blur-based validation. |
| onFocus | () => void | - | Callback fired when the textarea gains focus. |
| size | "xl" \| "lg" \| "md" \| "sm" \| "xs" | "md" | Size of the textarea. |
| disabled | boolean | false | Whether the textarea is disabled. |
| state | "default" \| "warning" \| "error" | "default" | Validation state driving the border highlight, independent of any message. Error takes precedence over warning. |
| errorMessage | string | - | Error message displayed below. Implies state="error". |
| warningMessage | string | - | Warning message displayed below. Implies state="warning". Ignored in the error state. |
| aria-label | string | - | Accessible label. |
| aria-describedby | string | - | ID of description element. |
| id | string | - | HTML id attribute. |
| testID | string | - | Test identifier. |
Padding by Size:
| Size | Padding | | :--- | :------ | | xl | 16px | | lg | 14px | | md | 12px | | sm | 10px | | xs | 10px |
Validation state tokens:
| State | Border | Message text |
| :------ | :---------------------------- | :--------------------------------------- |
| error | theme.colors.border.alert | theme.colors.content.alert.primary |
| warning | theme.colors.border.warning | theme.colors.content.warning.primary |
Accessibility
- Uses native
<textarea>element aria-invalidset for the error state only — a warning is not an invalid value, so it is never marked invalidaria-disabledfor disabled statearia-describedbylinks to the error or warning message when one is rendered- Error message has
role="alert"for immediate announcements - Warning message has
role="status"so it is announced politely, without interrupting the user - Focus indicator follows WCAG guidelines
- Because a border highlight alone is a colour-only signal, when using
statewithout a message make sure the corresponding validation text is rendered elsewhere and associated with the field viaaria-describedby
