Components/Textarea

Textarea

A multi-line input as one 380 by 64 box, where the edge and the text colour carry all five states and nothing else moves.

Textarea · StateLive in the browser · drawn to match the file
State
Placeholder
380 × 64 in the fileradius.lgbase.backgroundbase.inputbase.muted-foreground
Five states, one box

No size axis and no variants - a textarea is one shape whose edge and text colour carry every state. Filled is the only one that changes the type: base.muted-foreground for a placeholder, base.foreground once something has been typed. That difference is the whole reason Filled is drawn at all.

Focus moves the edge as well as the ring

The 1px edge goes from base.input to base.ring and a 3px ring at half opacity appears outside it. Invalid does the same trick in base.destructive, with its ring at 20%. Both numbers come straight from the source - ring-ring/50 and ring-destructive/20 - which is worth knowing because Input, its closest sibling, draws focus differently.

64 is a minimum, not a height

The source writes min-h-16 with field-sizing-content, so the box starts at 64 and grows with what is typed - and because it sets no resize rule, the reader can drag the corner too. A design file has neither typing nor dragging, so the file draws the floor. Plan the empty state from the component and the full one from the content.

In the browser

The one state a file cannot draw

Every state above is a still. This one is a real textarea at the file's measurements, so you can watch Default become Filled as you type, drag the grip in its corner the way the source's own docs let you, and watch the box grow past its 64 floor with field-sizing-content on.

State
field-sizing
resize
Default0 charactersfixed at 64drag the corner
Five places it earns its keep

A textarea is a promise about length

Its height is the only thing telling a reader how much you expect them to write. Everything here is about getting that promise right, and about what has to sit around the box for it to be usable.

Example
This appears on your public profile.

Label above, description below, both from Field. The description belongs under the box rather than under the label: by the time someone is reading it they are looking at the box, and text between a label and its control pushes the two apart for no gain.

Right to left

The caret starts at the other edge

The source's own docs carry an Arabic example, and it needs no new drawing. Under dir=rtl the placeholder, the caret and every line of text move to the right edge on their own. The box, the edge and the ring are symmetrical, so nothing about the component has to change.

dir=ltr
dir=rtl
Nothing is anchored

The whole component is one box with even padding on both sides. There is no icon, no addon and no clear button to move, which makes this the least work of any control in the kit to get right in a mirrored layout.

Padding is symmetric already

10 either side in the file, so there is no start-side inset to swap. If you ever give the box an asymmetric inset - room for a counter, say - use padding-inline-start rather than padding-left, or it will sit on the wrong edge.

What still needs a decision

The resize grip. Browsers park it at the bottom-right in both directions, and no property mirrors it - the one part of this component that does not follow dir. If that matters, set resize to none and let the box grow with its content instead.

In the file

One component, five states

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

TextareaStateDefault · Filled · Focus · Disabled · Invalid - 380 × 64, radius.lg, 14/20 type, 12 of horizontal padding against 8 of vertical5

Using the Textarea

Because a textarea has no sizes in the source - it is one box with min-h-16 and a width set by whatever it sits in. Height comes from the content rather than from a variant, so there is nothing to publish. If you need a taller box, resize the instance; you are changing a layout decision, not picking a variant.

The type colour, and only that. A placeholder is base.muted-foreground and typed text is base.foreground, which is the one thing telling a reader whether the field has been answered. The box, the edge and the padding are identical to Default, so the two variants exist purely to record that difference.

The 1px edge itself moves to base.ring, and a 3px ring at half opacity appears outside it. Invalid does the same in base.destructive with its ring at 20%. Both come straight from the source - ring-ring/50 and ring-destructive/20. Input, its closest sibling, draws focus as a ring at full alpha over an unchanged edge, so the two are worth comparing before you build a form out of both.

The fill goes to base.muted and the whole variant drops to 50%, while the base.input edge stays where it is. That last part matters in a form: Input is disabled the same way, so a stack of controls all reads as one kind of off rather than two. The source only dims, and adding the muted fill on top is the kit's own convention across every field it draws.

It is a floor, not a height. The source writes min-h-16 together with field-sizing-content, so the box starts at 64 and grows as the writing does. It also sets no resize rule, which leaves the browser default in place: a grip in the corner the reader can drag. Penpot has neither typing nor dragging, so the file draws the empty state. Plan the floor from the component and the full box from the content you actually expect.

Let the question decide. Two lines is right for a note or a reason; six is right when you know the answer is long, like a bug report; growing with the content is right almost everywhere else and is what the source does. Height is the only signal a reader gets about how much you expect them to write, and a 200-tall box under a yes-or-no question reliably gets nothing. Whatever you pick, the grip lets the reader overrule you, and that is worth leaving alone - turn resize off only when a taller box would break the layout around it.

Label above, description and counter below. The description under the box rather than under the label, because by the time someone reads it they are already looking at the box. A counter earns its place only when the limit is real and close - show it quietly, and let it turn base.destructive near the end rather than counting down from the start.

Yes, with dir on an ancestor and no other change. The placeholder, the caret and every line move to the right edge on their own, and there is nothing anchored to a side - no icon, no addon, no clear button - so the component needs nothing new. The source's own docs carry an Arabic example. The one thing direction does not fix is the resize handle, which browsers park at the bottom-right either way; if that matters, turn resizing off and let the box grow instead.

No - this page in the file is the Components board and nothing else. The worked examples on this page are the kit's answer to that: a Field pairing, a submit button, a counter, a review box, and the three ways of choosing a height.

Forms · 5 variants