Text Area
The text area component provides users with a resizable input field for entering longer text content. It includes features like character counting, validation states, and optional helper text to guide users through data entry tasks.
import {TextArea} from "@qualcomm-ui/react/text-area"Examples
Simple
The simple <TextArea> bundles all subcomponents together into a single component.
<TextArea
className="w-72"
hint="Optional hint"
label="Label"
placeholder="Placeholder text"
/>
Composite
Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.
<TextArea.Root className="w-72" maxLength={50}>
<TextArea.Label>Label</TextArea.Label>
<TextArea.Counter />
<TextArea.Input placeholder="Placeholder text" />
<TextArea.Hint>Optional hint</TextArea.Hint>
<TextArea.ErrorText>Optional error text</TextArea.ErrorText>
</TextArea.Root>
Counter
Display a character count using the counter prop or by setting a maxLength.
By default, the counter displays:
count/maxwhenmaxLengthis setcountwhen onlycounteris used
Use counterProps.display to customize the format with a function that receives the current count and optional max length.
<TextArea className="w-72" counter label="Counter without maxLength" />
<TextArea
className="w-72"
label="Counter with maxLength"
maxLength={10}
/>
<TextArea
className="w-72"
counterProps={{
display: (count, max) =>
max ? `${count} of ${max} characters` : `${count} characters`,
}}
label="Custom counter display"
maxLength={10}
/>
Sizes
Customize size using the size prop. The default size is md.
<TextArea className="w-56" defaultValue="sm" size="sm" />
<TextArea className="w-60" defaultValue="md" />
<TextArea className="w-64" defaultValue="lg" size="lg" />
States
The following shows how the TextArea component appears in each interactive state.
<TextArea disabled label="Disabled" placeholder="Disabled" />
<TextArea label="Read only" placeholder="Read only" readOnly />
<TextArea label="Required" placeholder="Required" required />
<TextArea
errorText="Invalid"
invalid
label="Invalid"
placeholder="Invalid"
/>
Controlled State
Set the initial value using the defaultValue prop, or use value and onValueChange to control the value manually. These props follow our controlled state pattern.
import {type ReactElement, useState} from "react"
import {Button} from "@qualcomm-ui/react/button"
import {TextArea} from "@qualcomm-ui/react/text-area"
export function TextAreaControlledStateDemo(): ReactElement {
const [value, setValue] = useState<string>("Controlled value")
return (
<div className="flex items-end gap-4">
<TextArea.Root
className="w-72"
onValueChange={(updatedValue) => {
console.debug("Value changed:", updatedValue)
setValue(updatedValue)
}}
value={value}
>
<TextArea.Label>Label</TextArea.Label>
<TextArea.Input placeholder="Placeholder text" />
</TextArea.Root>
<Button emphasis="primary" onClick={() => setValue("")} variant="outline">
Reset
</Button>
</div>
)
}Error Text and Indicator
Error messages are displayed using two props:
The error text and indicator will only render when invalid is true.
<TextArea
className="w-72"
errorText="You must enter at least 10 characters."
invalid={value.length < 10}
label="Label"
onValueChange={setValue}
placeholder="Enter at least 10 characters"
required
value={value}
/>
Forms
Choose the form library that fits your needs—we've built examples with React Hook Form and Tanstack Form to get you started.
React Hook Form
Use React Hook Form to handle the input state and validation. ArkType works great for schema validation if you need it.
import {arktypeResolver} from "@hookform/resolvers/arktype"
import {type} from "arktype"
import {Controller, useForm} from "react-hook-form"
import {Button} from "@qualcomm-ui/react/button"
import {TextArea} from "@qualcomm-ui/react/text-area"
interface FormData {
feedback: string
}
const FormSchema = type({
feedback: type("string>=10").configure({
message: "Feedback must be at least 10 characters long",
}),
})
export function TextAreaReactHookFormDemo() {
const {control, handleSubmit} = useForm<FormData>({
defaultValues: {feedback: ""},
resolver: arktypeResolver(FormSchema),
})
return (
<form
className="mx-auto flex w-full max-w-sm flex-col gap-3"
noValidate
onSubmit={(e) => {
void handleSubmit((data) => console.log(data))(e)
}}
>
<Controller
control={control}
name="feedback"
render={({field: {onChange, ...fieldProps}, fieldState: {error}}) => (
<TextArea
className="w-full"
counter
errorText={error?.message}
hint="Minimum 10 characters"
invalid={!!error}
label="Feedback"
onValueChange={onChange}
placeholder="Tell us about your experience"
required
{...fieldProps}
/>
)}
/>
<div className="flex w-full justify-end">
<Button emphasis="primary" type="submit" variant="fill">
Send Feedback
</Button>
</div>
</form>
)
}Tanstack Form
Tanstack Form handles validation with its built-in validators.
import {useForm} from "@tanstack/react-form"
import {Button} from "@qualcomm-ui/react/button"
import {TextArea} from "@qualcomm-ui/react/text-area"
interface FeedbackFormData {
feedback: string
}
export function TextAreaTanstackFormDemo() {
const form = useForm({
defaultValues: {
feedback: "",
} satisfies FeedbackFormData,
onSubmit: ({value}) => {
// Handle successful submission
console.log("Form submitted:", value)
},
})
return (
<form
className="mx-auto flex w-full max-w-sm flex-col gap-3"
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
<form.Field
name="feedback"
validators={{
onChange: ({value}) => {
if (!value || value.trim().length < 10) {
return "Feedback must be at least 10 characters long"
}
return undefined
},
}}
>
{(field) => (
<TextArea
className="w-full"
counter
errorText={field.state.meta.errors?.[0]}
hint="Minimum 10 characters"
invalid={field.state.meta.errors.length > 0}
label="Feedback"
name={field.name}
onBlur={field.handleBlur}
onValueChange={field.handleChange}
placeholder="Tell us about your experience"
value={field.state.value}
/>
)}
</form.Field>
<div className="flex w-full justify-end">
<Button
disabled={form.state.isSubmitting}
emphasis="primary"
type="submit"
variant="fill"
>
Send Feedback
</Button>
</div>
</form>
)
}Tanstack form also supports ArkType.
API
<TextArea />
The TextArea extends the TextArea.Root with the following props:
| Prop | Type |
|---|---|
Controls whether to display the counter element. - true: always show the counter- false: never show the counter- undefined (default): only show the counter if maxLength is set | boolean |
Props applied to the counter element. | { |
string | |
Props applied to the error text element. | { |
Optional hint describing the element. This element is automatically
associated with the component's textarea element for accessibility. | |
Props applied to the hint element. | { |
Props applied to the textarea element. | any |
Optional label describing the element. Recommended. This element is
automatically associated with the component's input element for
accessibility. | |
Props applied to the label element. | { |
HTML placeholder attribute,
passed to the underlying textarea element. | string |
boolean-
true: always show the counter-
false: never show the counter-
undefined (default): only show the counter if maxLength is set{
display?: (
count: number,
maxLength?: number,
) => ReactNode
render?:
| Element
| ((
props: Props,
) => Element)
}
{
children?: ReactNode
render?:
| Element
| ((
props: Props,
) => Element)
}
{
children?: ReactNode
render?:
| Element
| ((
props: Props,
) => Element)
}
any{
children?: ReactNode
render?:
| Element
| ((
props: Props,
) => Element)
}
stringComposite API
<TextArea.Root>
| Prop | Type | Default |
|---|---|---|
The initial value of the input when rendered.
Use when you don't need to control the value of the input. | string | |
The document's text/writing direction. | 'ltr' | 'rtl' | 'ltr' |
Whether the input is disabled. When true, prevents user interaction and
applies visual styling to indicate the disabled state. | boolean | |
The id of the form that the input belongs to. | string | |
A root node to correctly resolve document in custom environments. i.e.,
Iframes, Electron. | () => | |
The ids of the elements that are associated with the input. These will be
automatically generated if omitted. | { | |
Controls the visual error state of the input. When true, applies semantic error
styling to indicate validation failure. | boolean | |
The maximum number of characters allowed in the textarea. | number | |
The name of the input field. Useful for form submission. | string | |
The callback invoked when the field is focused or blurred. | ( | |
The callback invoked when the value changes. | ( | |
Whether the input is read-only. When true, prevents user interaction while
keeping the input focusable and visible. | boolean | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement | |
Whether the input is required. When true, the input must have a value for form
validation to pass. | boolean | |
The size of the textarea and its elements. Governs properties like font size,
and item padding. | | 'sm' | 'md' |
The controlled value of the input | string |
string'ltr' | 'rtl'
booleanstring() =>
| Node
| ShadowRoot
| Document
{
counter: string
errorText: string
hint: string
input: string
label: string
}
booleannumberstring(
focused: boolean,
) => void
(
value: string,
) => void
boolean| ReactElement
| ((
props: object,
) => ReactElement)
boolean| 'sm'
| 'md'
| 'lg'
string| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__root' |
data-disabled | |
data-focus | |
data-invalid | |
data-part | 'root' |
data-scope | 'text-area' |
data-size | | 'sm' |
className'qui-text-area__root'data-disableddata-focusdata-invaliddata-part'root'data-scope'text-area'data-size| 'sm'
| 'md'
| 'lg'
<TextArea.Label>
<label> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__label' |
data-disabled | |
data-focus | |
data-invalid | |
data-part | 'label' |
data-scope | 'text-area' |
data-size | | 'sm' |
className'qui-text-area__label'data-disableddata-focusdata-invaliddata-part'label'data-scope'text-area'data-size| 'sm'
| 'md'
| 'lg'
<TextArea.Hint>
<div> element by default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__hint' |
data-disabled | |
data-part | 'hint' |
data-scope | 'text-area' |
hidden | boolean |
className'qui-text-area__hint'data-disableddata-part'hint'data-scope'text-area'hiddenboolean<TextArea.Counter>
<div>
element by default.| Prop | Type |
|---|---|
Customize how the counter is displayed. Receives the current character
count and optional max length, and returns a React node. | ( |
// Display as "42 of 100" | |
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
(
count: number,
maxLength?: number,
) => ReactNode
// Display as "42 of 100"
display={(count, max) => max ? `${count} of ${max}` : count}
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__counter' |
data-disabled | |
data-focus | |
data-invalid | |
data-max | number |
data-part | 'counter' |
data-scope | 'text-area' |
data-size | | 'sm' |
className'qui-text-area__counter'data-disableddata-focusdata-invaliddata-maxnumberdata-part'counter'data-scope'text-area'data-size| 'sm'
| 'md'
| 'lg'
<TextArea.Input>
<textarea> element.| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__input' |
data-disabled | |
data-empty | |
data-focus | |
data-invalid | |
data-part | 'input' |
data-readonly | |
data-scope | 'text-area' |
data-size | | 'sm' |
className'qui-text-area__input'data-disableddata-emptydata-focusdata-invaliddata-part'input'data-readonlydata-scope'text-area'data-size| 'sm'
| 'md'
| 'lg'
<TextArea.ErrorText>
<div> element by
default.| Prop | Type |
|---|---|
Allows you to replace the component's HTML element with a different tag or component. Learn more | | ReactElement |
| ReactElement
| ((
props: object,
) => ReactElement)
| Attribute / Property | Value |
|---|---|
className | 'qui-text-area__error-text' |
data-part | 'error-text' |
data-scope | 'text-area' |
hidden | boolean |
className'qui-text-area__error-text'data-part'error-text'data-scope'text-area'hiddenboolean
<div>element by default.