Skip to main content
PolyUI/docs

文本域

多行文本输入框,支持受控模式、禁用状态与自定义行数。

安装

bash
npx polyui add textarea

基础

tsx
import { Textarea } from "@polyui/react/textarea"
export function TextareaBasic() {
  return <Textarea placeholder="Type your message here." className="w-full max-w-xs" />
}

带标签

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>
  )
}

错误状态

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>
  )
}

占位符

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>
  )
}

禁用

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>
  )
}

只读

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>
  )
}

带按钮

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>
  )
}

自动增高

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>
  )
}

填充样式

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>
  )
}

尺寸

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>
  )
}

前置图标

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>
  )
}

末端图标

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>
  )
}

悬浮标签

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>
  )
}

浮动标签

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>
  )
}

内嵌标签

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>
  )
}

禁止缩放

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>
  )
}

字符计数

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>
  )
}

属性

属性类型默认值说明
placeholderstring输入框的占位文本,内容为空时显示。
disabledbooleanfalse禁用文本域,阻止交互并降低视觉透明度。
rowsnumber5文本域的初始可见行数,默认为 5。
valuestring受控模式下的文本内容。
onChange(e: React.ChangeEvent<HTMLTextAreaElement>) => void文本内容变化时的回调函数。