Text Area
1.6.0The 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.
Explorer
API
<TextArea />
The TextArea extends the TextArea.Root with the following props:
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>
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'
stringclassName'qui-text-area__root'data-disableddata-focusdata-invaliddata-size| 'sm'
| 'md'
| 'lg'
data-text-area-part'root'<TextArea.Label>
<label> element by default.| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-text-area__label'data-disableddata-focusdata-invaliddata-size| 'sm'
| 'md'
| 'lg'
data-text-area-part'label'<TextArea.Hint>
<div> element by default.| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-text-area__hint'data-disableddata-text-area-part'hint'hiddenboolean<TextArea.Counter>
<div>
element by default.(
count: number,
maxLength?: number,
) => ReactNode
// Display as "42 of 100"
display={(count, max) => max ? `${count} of ${max}` : count}
| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-text-area__counter'data-disableddata-focusdata-invaliddata-maxnumberdata-size| 'sm'
| 'md'
| 'lg'
data-text-area-part'counter'<TextArea.Input>
<textarea> element.className'qui-text-area__input'data-disableddata-emptydata-focusdata-invaliddata-readonlydata-size| 'sm'
| 'md'
| 'lg'
data-text-area-part'input'<TextArea.ErrorText>
<div> element by
default.| ReactElement
| ((
props: object,
) => ReactElement)
className'qui-text-area__error-text'data-text-area-part'error-text'hiddenboolean
<div>element by default.