Textarea
Several lines of text that grow as people write.
Preview
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/veneerTry it
Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.
import { Textarea } from '@kerf-js/veneer'<Textarea label="Description" hint="Shown on the project's card." />Usage
import { Textarea } from '@kerf-js/veneer'Variants
Use variant and appearance to change the field's style, here classic and line.
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.
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.
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.
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
- Input, when one line is enough
- RichTextEditor, when people need formatting
- MentionsInput, when people tag each other
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
| Key | Own keys | States |
|---|---|---|
parts.field | bg, radius | hover, focus, press, active, open, disabled |
parts.field.label | text | hover, focus |
veneer({ colors: { brand: '#e8590c', ink: '#1b1b1f' }, parts: { field: { radius: 4, label: { focus: { text: 'brand' } } }, },})API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The name people read and screen readers announce. Always required, and it floats on the border by default. |
hint | unknown | — | A short line of help under the field. |
error | unknown | — | Replaces the hint and marks the field invalid. |
icon | IconRef | — | Leading icon: it sweeps with the border. |
prefix | unknown | — | Text or an element inside the field before the value, such as $. |
suffix | unknown | — | 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. |
clearable | boolean | false | A clear button (shown once there is a value). |
counter | number | — | The most it should hold. Draws a count under the field. |
id | string | — | The element id. One is made for you if you leave it out. |
value | string | — | The current value, when you own the state. |
disabled | boolean | false | Can't be focused or changed, and doesn't post with a form. |
readonly | boolean | false | Can be focused, read and copied, but not changed. It still posts. |
placeholder | string | — | Sample text shown while empty. Never a substitute for a label. |
rows | number | 3 | The starting height in lines. |
| Event | Type | Default | Description |
|---|---|---|---|
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