Textarea

A multi-line text field for longer user input.

import { ... } from '@corndev/ui'

0 / 200 characters

HTML

No JavaScript required — these classes are the whole component.

<label class="corn-label" for="message">Message</label>
<textarea id="message" class="corn-textarea" data-size="md" data-resize="vertical"></textarea>

<!-- Grows as it is typed into, starting at 3 lines and scrolling past 12.
     No JavaScript: the growing is field-sizing, in the stylesheet. -->
<label class="corn-label" for="description">Description</label>
<textarea
  id="description"
  class="corn-textarea"
  data-autosize
  data-max-rows
  rows="3"
  style="--corn-textarea-max-rows: 12"
></textarea>

Classes

Class
.corn-textarea

Attributes

AttributeValues
data-sizesm|md|lg
data-autosizepresent or absent
data-max-rowspresent or absent
data-resizenone|vertical|horizontal|both
data-errorpresent or absent
data-full-widthpresent or absent

Vanilla JS

Optional. createTextarea only adds and removes the classes and attributes above, so the HTML is equivalent. Reach for it when you need to change options at runtime.

import { createTextarea } from '@corndev/ui';
import '@corndev/ui/theme';

const el = document.querySelector('#target');
const instance = createTextarea(el, { /* options */ });

Options

OptionTypeDefaultDescription
size'sm'|'md'|'lg''md'
disabledbooleanfalse
errorbooleanfalse
fullWidthbooleanfalse
autosizebooleanfalseGrows with what is typed, and shrinks again when text is deleted. Pure CSS — field-sizing — so this installs no listener and measures nothing; the markup can carry data-autosize on its own and never call this function.
rowsnumberundefinedStarting height, in lines. With autosize it is the floor the box never goes below; without it, simply the height. Set here it writes the rows attribute, so plain markup can state it instead.
maxRowsnumberundefinedStops the growth at this many lines and scrolls beyond it. Only meaningful with autosize.
resize'none'|'vertical'|'horizontal'|'both'undefinedDefaults to vertical, or to none when autosize is set — a drag handle fights a box that sizes itself.

React

Textarea — from @corndev/ui-react.

The props are the same as the options above, plus className.

Default

<Label htmlFor="message">Message</Label>
<Textarea id="message" placeholder="Enter a longer message..." />

Sizes

<Textarea size="sm" aria-label="Small" />
<Textarea size="md" aria-label="Medium" />
<Textarea size="lg" aria-label="Large" />

Growing with its content

autosize makes the box follow what is typed into it, and shrink again when text is deleted. It is done in CSS with field-sizing, so there is nothing to measure, no listener to install, and the vanilla package gets it from the markup alone.

<Textarea autosize rows={2} aria-label="Description" />

rows is the floor, not just the starting height — the box never goes below it. Stating it matters more than it looks: under field-sizing an empty rows={3} textarea would otherwise collapse to a single line, so the stylesheet sets the minimum explicitly.

The drag handle is turned off by default when autosize is on, since dragging fights a box that sizes itself. Pass resize to have both.

Without React

The growing is entirely in the stylesheet, so plain markup does it too:

<textarea class="corn-textarea" data-autosize rows="3"></textarea>

Capping the growth

A 200-line SQL definition has to stop growing somewhere. maxRows sets the ceiling; past it the textarea scrolls as usual.

<Textarea autosize rows={2} maxRows={5} aria-label="SQL definition" />

Inside a scrolling panel

This is the case that goes wrong in a real layout. A textarea that grows, inside a panel that grows, inside a page that also scrolls, is how you end up with three scrollbars.

Give the panel a height and let it scroll. The textarea then grows within it and the page never moves — measured in Chromium and Firefox: with 30 lines typed in, the 120px panel stays 120px, the panel scrolls, and the document does not.

<div style={{ blockSize: '120px', overflow: 'auto' }}>
  <Textarea autosize rows={2} fullWidth aria-label="Notes" />
</div>

A maxRows low enough to fit the panel is the other way round at the same problem, and the two combine: the box stops growing before it can overflow, and scrolls its own content instead.

Resize behaviour

<Textarea resize="vertical" aria-label="Notes" />
<Textarea resize="none" aria-label="Notes" />
<Textarea resize="both" aria-label="Notes" />

Error state

<Textarea error aria-label="Message" />

Disabled

<Textarea disabled aria-label="Message" />