Skip to content
kerfjsGitHub

Textarea

Several lines of text that grow as people write.

SourcePackage @kerf-js/veneerTry it

Preview

tsx
import { Stack, Textarea } from '@kerf-js/veneer'export function Rows() {  return (  <Stack gap="group">    <Textarea      label="Description"      rows={2}      defaultValue={description}    />    <Textarea      label="Description"      rows={5}      defaultValue={description}    />  </Stack>  )}const description =  'The public shop, served from the edge.'

Installation

npm install @kerf-js/veneer

Try it

Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.

Live preview
Controls
Rows3
Variant
Codeupdates as you change the controls
import { Textarea } from '@kerf-js/veneer'<Textarea label="Description" hint="Shown on the project's card." />

Usage

tsx
import { Textarea } from '@kerf-js/veneer'

Variants

Use variant and appearance to change the field's style, here classic and line.

tsx
import { Grid, Textarea } from '@kerf-js/veneer'export function Variants() {  return (  <Grid    cols={3}    gap="group"  >    <Textarea      label="Description"      defaultValue="The public shop."    />    <Textarea      label="Description"      defaultValue="The public shop."      variant="classic"    />    <Textarea      label="Description"      defaultValue="The public shop."      appearance="line"    />  </Grid>  )}

Counter

Use counter to set a character limit and show how much of it is used.

tsx
import { Textarea } from '@kerf-js/veneer'export function Counter() {  return (  <Textarea    label="Release note"    counter={140}    defaultValue={      'Product images now load from the ' +      'edge, about 40% faster.'    }  />  )}

States

A textarea can show a hint or an error, and can be disabled or readonly.

tsx
import { Grid, Textarea } from '@kerf-js/veneer'import { Stack, Text } from '@kerf-js/veneer'export function States() {  return (  <Grid    cols={3}    gap="group"  >    <Cell name="Empty">      <Textarea label="Description" />    </Cell>    <Cell name="Filled">      <Textarea        label="Description"        defaultValue={description}      />    </Cell>    <Cell name="Hint">      <Textarea        label="Description"        hint="Shown on the project's card."      />    </Cell>    <Cell name="Invalid">      <Textarea        label="Description"        defaultValue="x"        error="Write at least ten characters."      />    </Cell>    <Cell name="Disabled">      <Textarea        label="Description"        defaultValue={description}        disabled      />    </Cell>    <Cell name="Read-only">      <Textarea        label="Description"        defaultValue={description}        readonly      />    </Cell>  </Grid>  )}const Cell = ({  name,  children,}: {  name: string  children?: unknown}) => (  <Stack gap="tight">    <Text      role="caption"      subtle    >      {name}    </Text>    {children}  </Stack>)const description =  'The public shop, served from the edge.'

Post a release note

Write something and press ⌘ Enter.

tsx
import { state } from '@kerf-js/core'import {  Button,  Row,  Stack,  Text,  Textarea} from '@kerf-js/veneer'export function ReleaseNote() {  const text = state('')  const notes = state([    'Lowered the stale window to 30s.',  ])  const post = (e: Event) => {    e.preventDefault()    const note = text.value.trim()    if (!note) return    notes.value = [note, ...notes.value]    text.value = ''  }  return (    <Stack      as="form"      gap="group"      onSubmit={post}    >      <Textarea        label="Release note"        counter={140}        hint="⌘ Enter posts it."        value={text.value}        onInput={(e) =>          (text.value = e.currentTarget.value)        }      />      <Row justify="end">        <Button          type="submit"          aria-disabled={            text.value.trim() ? undefined : 'true'          }        >          Post note        </Button>      </Row>      <Stack gap="tight">        {notes.value.map((n) => (          <Text            key={n}            subtle          >            {n}          </Text>        ))}      </Stack>    </Stack>  )}

When to use

Use it for

  • Descriptions, notes and messages

Reach for something else

Accessibility

Enter
A new line
⌘Enter
Submits its form
  • The same label, hint and error wiring as Input.

Theming

Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works

KeyOwn keysStates
parts.fieldbg, radiushover, focus, press, active, open, disabled
parts.field.labeltexthover, focus
ts
veneer({  colors: { brand: '#e8590c', ink: '#1b1b1f' },  parts: {    field: { radius: 4, label: { focus: { text: 'brand' } } },  },})

API Reference

PropTypeDefaultDescription
labelstring—The name people read and screen readers announce. Always required, and it floats on the border by default.
hintunknown—A short line of help under the field.
errorunknown—Replaces the hint and marks the field invalid.
iconIconRef—Leading icon: it sweeps with the border.
prefixunknown—Text or an element inside the field before the value, such as $.
suffixunknown—Text or an element inside the field after the value, such as kg.
variant"floating" | "classic"—floating puts the label on the border, classic above the field.
appearance"line"—line draws only the bottom edge.
size"sm" | "md" | "lg"—sm, md or lg. Each follows the density around it.
clearablebooleanfalseA clear button (shown once there is a value).
counternumber—The most it should hold. Draws a count under the field.
idstring—The element id. One is made for you if you leave it out.
valuestring—The current value, when you own the state.
disabledbooleanfalseCan't be focused or changed, and doesn't post with a form.
readonlybooleanfalseCan be focused, read and copied, but not changed. It still posts.
placeholderstring—Sample text shown while empty. Never a substitute for a label.
rowsnumber3The starting height in lines.
EventTypeDefaultDescription
onInput((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen…—Called on each keystroke.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="textarea"] [data-part="input"]

input

RelatedInputRich text editorMentions input