Textarea
A multi-line text input with support for controlled mode, disabled state, and custom row count.
Installation
bash
npx polyui add textareaBasic
tsx
import { Textarea } from "@polyui/react/textarea"
export function TextareaBasic() {
return <Textarea placeholder="Type your message here." className="w-full max-w-xs" />
}With Label
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaWithLabel() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Your message</Label>
<Textarea placeholder="Type your feedback here." id={id} />
</div>
)
}Invalid
Please provide useful feedback.
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaInvalid() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Invalid</Label>
<Textarea aria-invalid placeholder="Type your feedback here." id={id} />
<p className="text-destructive text-xs">Please provide useful feedback.</p>
</div>
)
}Placeholder
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaPlaceholder() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Placeholder</Label>
<Textarea id={id} placeholder="Enter your message..." />
</div>
)
}Disabled
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaDisabled() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Disabled</Label>
<Textarea placeholder="Type your feedback here." disabled id={id} />
</div>
)
}Read Only
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaReadOnly() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Read Only</Label>
<Textarea
className="read-only:bg-muted"
defaultValue="Read only text"
placeholder="Type your feedback here."
id={id}
readOnly
/>
</div>
)
}With Button
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
import { Button } from "@polyui/react/button"
export function TextareaWithButton() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>With Button</Label>
<Textarea id={id} placeholder="Type your feedback here." />
<Button size="sm">Submit Feedback</Button>
</div>
)
}Auto Grow
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaAutoGrow() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Auto Grow</Label>
<Textarea
id={id}
placeholder="Type your feedback here."
className="field-sizing-content max-h-30 min-h-0 resize-none py-1.75"
/>
</div>
)
}Filled
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaFilled() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Filled</Label>
<Textarea className="bg-muted border-transparent shadow-none" placeholder="Type your feedback here." id={id} />
</div>
)
}Sizes
tsx
import { Textarea } from "@polyui/react/textarea"
export function TextareaSizes() {
return (
<div className="w-full max-w-xs space-y-2">
<Textarea className="min-h-10 py-1.5" placeholder="Small" />
<Textarea placeholder="Medium" />
<Textarea className="min-h-20 py-2.5" placeholder="Large" />
</div>
)
}Start Icon
Address
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
import { HomeIcon } from "lucide-react"
export function TextareaStartIcon() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Start Icon</Label>
<div className="relative">
<div className="text-muted-foreground pointer-events-none absolute top-2.5 left-0 flex items-center justify-center pl-3 peer-disabled:opacity-50">
<HomeIcon className="size-4" />
<span className="sr-only">Address</span>
</div>
<Textarea id={id} placeholder="Address" className="peer pl-9" />
</div>
</div>
)
}End Icon
Address
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
import { HomeIcon } from "lucide-react"
export function TextareaEndIcon() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>End Icon</Label>
<div className="relative">
<div className="text-muted-foreground pointer-events-none absolute top-2.5 right-0 flex items-center justify-center pr-3 peer-disabled:opacity-50">
<HomeIcon className="size-4" />
<span className="sr-only">Address</span>
</div>
<Textarea id={id} placeholder="Address" className="peer pr-9" />
</div>
</div>
)
}Overlapping Label
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaOverlappingLabel() {
const id = useId()
return (
<div className="relative w-full max-w-xs space-y-2">
<Label
htmlFor={id}
className="bg-background text-foreground absolute top-0 left-2 z-10 block -translate-y-1/2 px-1 text-xs font-medium group-has-disabled:opacity-50"
>
Overlapping Label
</Label>
<Textarea id={id} className="!bg-background" />
</div>
)
}Floating Label
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaFloatingLabel() {
const id = useId()
return (
<div className="group relative w-full max-w-xs space-y-2">
<label
htmlFor={id}
className="origin-start text-muted-foreground/70 group-focus-within:text-foreground has-[+textarea:not(:placeholder-shown)]:text-foreground absolute top-0 block translate-y-2 cursor-text px-2 text-sm transition-all group-focus-within:pointer-events-none group-focus-within:-translate-y-1/2 group-focus-within:cursor-default group-focus-within:text-xs group-focus-within:font-medium has-[+textarea:not(:placeholder-shown)]:pointer-events-none has-[+textarea:not(:placeholder-shown)]:-translate-y-1/2 has-[+textarea:not(:placeholder-shown)]:cursor-default has-[+textarea:not(:placeholder-shown)]:text-xs has-[+textarea:not(:placeholder-shown)]:font-medium"
>
<span className="bg-background inline-flex px-1">Floating Label</span>
</label>
<Textarea id={id} placeholder=" " className="!bg-background" />
</div>
)
}Inset Label
tsx
import { useId } from "react"
import { Label } from "@polyui/react/label"
export function TextareaInsetLabel() {
const id = useId()
return (
<div className="border-input bg-background focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive relative w-full max-w-xs rounded-md border shadow-xs transition-[color,box-shadow] outline-none focus-within:ring-[3px] has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50">
<label htmlFor={id} className="text-foreground block px-3 pt-1 text-xs font-medium">
Inset Label
</label>
<textarea
id={id}
className="text-foreground placeholder:text-muted-foreground/70 flex min-h-14 w-full px-3 pb-2 text-sm focus-visible:outline-none"
/>
</div>
)
}No Resize
tsx
import { useId } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaNoResize() {
const id = useId()
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>No Resize</Label>
<Textarea id={id} placeholder="Type your feedback here." className="[resize:none]" />
</div>
)
}Character Count
200 characters left
tsx
import { useId, useState } from "react"
import { Textarea } from "@polyui/react/textarea"
import { Label } from "@polyui/react/label"
export function TextareaCharacterCount() {
const [value, setValue] = useState("")
const id = useId()
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
if (e.target.value.length <= MAX_LENGTH) setValue(e.target.value)
}
return (
<div className="w-full max-w-xs space-y-2">
<Label htmlFor={id}>Character Count</Label>
<Textarea
placeholder="Type your feedback here."
value={value}
maxLength={MAX_LENGTH}
onChange={handleChange}
id={id}
/>
<p className="text-muted-foreground text-xs">
<span className="tabular-nums">{MAX_LENGTH - value.length}</span> characters left
</p>
</div>
)
}Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
placeholder | string | — | Placeholder text shown when the textarea is empty. |
disabled | boolean | false | Disables the textarea, preventing interaction and reducing visual opacity. |
rows | number | 5 | Initial visible row count. Defaults to 5. |
value | string | — | The controlled text content of the textarea. |
onChange | (e: React.ChangeEvent<HTMLTextAreaElement>) => void | — | Callback fired when the text content changes. |