Textarea
Wielowierszowe pole tekstowe, które rośnie razem z treścią — na wiadomości, opisy i opinie.
import { Field, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
export default function TextareaDemo() {
return (
<Field className="w-80">
<FieldLabel htmlFor="textarea-demo">Co możemy poprawić?</FieldLabel>
<Textarea
id="textarea-demo"
placeholder="Przycisk eksportu trudno znaleźć na telefonie…"
/>
</Field>
)
}Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/textarea.jsonUżycie
import { Field, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"<Field>
<FieldLabel htmlFor="message">Message</FieldLabel>
<Textarea id="message" placeholder="Tell us what happened…" />
</Field>Textarea ma to samo obramowanie, wypełnienie, poświatę fokusu i styl błędu co Input, więc oba komponenty pasują do siebie w każdym formularzu — bez żadnych wyjątków w kodzie.
Przykłady
Rośnie razem z treścią
Textarea korzysta z field-sizing: content: zaczyna od wygodnych trzech wierszy i rośnie w trakcie pisania, więc nikt nie musi pisać przez dziurkę od klucza. Ogranicz wzrost klasą max-h-* — powyżej tej wysokości pole zaczyna się przewijać. W przeglądarkach bez obsługi field-sizing pole zachowuje minimalną wysokość, a jego rozmiar można zmienić przeciąganiem.
Rośnie wraz z treścią do 240 px, a potem się przewija.
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
export default function TextareaAutoResize() {
return (
<Field className="w-full max-w-md">
<FieldLabel htmlFor="textarea-auto">Informacje o wersji</FieldLabel>
<Textarea
id="textarea-auto"
className="max-h-60"
defaultValue={
"Szybszy eksport: pliki CSV i XLSX są generowane w tle.\nNowość: skróty klawiszowe dla każdej akcji w tabeli.\nPoprawka: filtry dat uwzględniają teraz strefę czasową przestrzeni roboczej."
}
/>
<FieldDescription>
Rośnie wraz z treścią do 240 px, a potem się przewija.
</FieldDescription>
</Field>
)
}Limit znaków
Pokaż limit, zanim ktoś do niego dojdzie, odliczaj znaki w trakcie pisania i przełącz się na błąd po jego przekroczeniu — zamiast po cichu ucinać tekst. Licznik jest powiązany przez aria-describedby; osobny region na żywo o niskim priorytecie (polite) odzywa się dopiero blisko limitu, żeby nie przerywać osobom korzystającym z czytnika ekranu przy każdym naciśnięciu klawisza.
Widoczny na twoim profilu publicznym.
103/160
"use client"
import * as React from "react"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
const LIMIT = 160
export default function TextareaCharacterLimit() {
const [bio, setBio] = React.useState(
"Design engineer w prfct. Zależy mi na typografii, animacjach i interfejsach, które nie wchodzą w drogę."
)
const remaining = LIMIT - bio.length
const over = remaining < 0
return (
<Field data-invalid={over || undefined} className="w-full max-w-md">
<FieldLabel htmlFor="textarea-limit">Opis</FieldLabel>
<Textarea
id="textarea-limit"
value={bio}
onChange={(event) => setBio(event.target.value)}
aria-invalid={over || undefined}
aria-describedby="textarea-limit-count"
/>
{/* Announce only near the limit — not on every keystroke. */}
<span className="sr-only" aria-live="polite">
{remaining <= 20
? over
? `Znaków ponad limit: ${-remaining}`
: `Pozostało znaków: ${remaining}`
: ""}
</span>
<div className="flex items-start justify-between gap-4">
{over ? (
<FieldError>{`Opis nie może przekraczać ${LIMIT} znaków.`}</FieldError>
) : (
<FieldDescription>Widoczny na twoim profilu publicznym.</FieldDescription>
)}
<FieldDescription
id="textarea-limit-count"
className={
over
? "shrink-0 text-danger-11 tabular-nums"
: "shrink-0 tabular-nums"
}
>
{bio.length}/{LIMIT}
</FieldDescription>
</div>
</Field>
)
}Wyłączone i tylko do odczytu
Wyłączone pole nie przyjmuje fokusu i nie trafia do danych formularza. Pole tylko do odczytu nadal przyjmuje fokus, a jego treść można zaznaczać i przewijać — używaj go do tekstu, który trzeba przeczytać lub skopiować, na przykład wpisu z dziennika audytu.
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
export default function TextareaStates() {
return (
<FieldGroup className="grid w-full max-w-xl gap-4 sm:grid-cols-2">
<Field data-disabled>
<FieldLabel htmlFor="textarea-disabled">Notatki wewnętrzne</FieldLabel>
<Textarea
id="textarea-disabled"
disabled
defaultValue="Zablokowane na czas przetwarzania faktury."
/>
</Field>
<Field>
<FieldLabel htmlFor="textarea-readonly">Wpis audytu</FieldLabel>
<Textarea
id="textarea-readonly"
readOnly
defaultValue="Plan zmieniony z Team na Enterprise przez ada@prfct.dev 12 września."
/>
</Field>
</FieldGroup>
)
}W formularzu komentarza
Pole tekstowe często ma własny, niewielki zestaw akcji. Akcję główną umieść na końcu, a pole zrób na tyle szerokie, żeby zmieściło prawdziwe zdanie.
"use client"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
export default function TextareaCommentForm() {
return (
<form
className="flex w-full max-w-lg gap-3"
onSubmit={(event) => event.preventDefault()}
>
<Avatar size="lg">
<AvatarFallback>GH</AvatarFallback>
</Avatar>
<div className="flex flex-1 flex-col gap-2">
<Textarea
name="comment"
placeholder="Napisz komentarz…"
aria-label="Dodaj komentarz"
className="min-h-24"
/>
<div className="flex justify-end gap-2">
<Button type="button" variant="ghost" size="sm">
Anuluj
</Button>
<Button type="submit" size="sm">
Dodaj komentarz
</Button>
</div>
</div>
</form>
)
}Wytyczne
Kiedy używać
- Odpowiedzi otwarte, dłuższe niż jeden wiersz: opinie, opisy, notki biograficzne, wiadomości.
- Treści, w których liczą się podziały wierszy, na przykład informacje o wydaniu albo adresy.
Kiedy nie używać
- Krótkie odpowiedzi — Input lepiej sygnalizuje „odpowiedz krótko”.
- Rozbudowane formatowanie, takie jak pogrubienia, listy czy wzmianki — użyj edytora tekstu sformatowanego.
- Pole wiadomości w czacie z załącznikami i przyciskami wysyłania — złóż je z Input Group i
InputGroupTextarea.
Rozmiar na miarę oczekiwanej odpowiedzi
Początkowa wysokość buduje oczekiwania. Pole na jeden wiersz sugeruje krótką odpowiedź; trzy do pięciu wierszy zachęcają do przemyślanej. Nie wyłączaj zmiany rozmiaru, chyba że układ naprawdę na to nie pozwala — polegają na niej osoby, które ustawiły większy tekst.
Opcjonalnie · do 500 znaków
Dostępność
Textarea to natywny <textarea>: czytniki ekranu przedstawiają go jako wielowierszowe pole edycji, a wszystkie funkcje przeglądarki działają bez dodatkowej pracy — w tym sprawdzanie pisowni, dyktowanie i wprowadzanie tekstu przez IME.
- Nadaj etykietę. Powiąż widoczny
FieldLabelprzezhtmlFor/idalbo dodajaria-label. - Opisz limity. Limity i wskazówki dotyczące formatu umieść w
FieldDescriptionpowiązanym przezaria-describedby, a nie w placeholderze. - Informuj oszczędnie. Licznik w regionie na żywo, który odzywa się przy każdym naciśnięciu klawisza, zagłusza to, co ktoś wpisuje. Komunikat powinien pojawić się dopiero wtedy, gdy tekst zbliża się do limitu lub go przekracza.
- Kontrast i rozmiar. Obramowanie ma kontrast co najmniej 3:1 względem strony, a tekst na małych ekranach ma 16 px, żeby iOS nie powiększał widoku.
| Klawisz | Działanie |
|---|---|
Tab | Przenosi fokus do pola. |
Enter | Wstawia podział wiersza. Nigdy nie wysyła formularza. |
CtrlEnter⌘Enter | Popularna konwencja wysyłania z pola wielowierszowego — podłącz ją samodzielnie, jeśli pasuje do scenariusza. |
Dokumentacja API
Textarea
Renderuje natywny element <textarea> i przyjmuje wszystkie jego atrybuty.