Textarea
July 6, 2026 · View on GitHub
Multi-line text entry for longer free-form input.
Contract
Textarea captures multi-line text — descriptions, messages, long-form fields. It shares Input's size/state grammar so an Input and Textarea with the same size sit flush in a form column. It adds two features Input lacks: a resize handle (both axes by default) and a character counter for length-constrained fields.
Anatomy
Container (host)
├─ Textarea field (native <textarea>, fills host; flex 1)
├─ Counter (absolute bottom-right, when showCounter is true)
└─ Grip (absolute bottom-right, custom SVG — shown when resize !== 'none')
- Container / host — border, radius, background; hosts the textarea and absolute overlays
- Textarea field — native `
- \text{Counter} — "\text{N}/\text{maxLength}" \text{text}, \text{absolute} \text{bottom}-\text{right}, \text{color} \text{gray}/400, \text{has} \text{the} \text{host}'\text{s} \text{background} \text{color} \text{so} \text{it} \text{reads} \text{cleanly} \text{over} \text{text}
- \text{Grip} — 10 \times 10 \text{SVG} \text{with} \text{two} \text{diagonal} \text{strokes}, \text{absolute} \text{bottom}-\text{right}, $pointer-events: none
so the native resize drag still fires through it (the native::-webkit-resizer` is visually hidden)
API
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Component size — matches Input sm/md/lg |
placeholder | string | '' | Placeholder text |
rows | number | 3 | Default visible rows |
maxLength | number | null | null | Character limit (shown by counter) |
showCounter | boolean | false | Show the "N/maxLength" counter |
resize | 'both' | 'vertical' | 'horizontal' | 'none' | 'both' | Direction the user can drag to resize |
filled | boolean | false | Filled variant (gray bg, no border) |
disabled | boolean | false | Non-interactive |
forceState | KpState | null | null | Force visual state — Storybook only |
Outputs
Implements ControlValueAccessor — use [(ngModel)] or reactive-form bindings. No custom outputs.
Content projection
None — Textarea is a leaf component.
Variants
| Dimension | Values |
|---|---|
| Size | sm (min-height 60), md (min-height 72), lg (min-height 88) |
| Variant | default (white + border), filled (gray bg, transparent border) |
| State | rest, hover, active, focus, disabled, error |
| Resize | both (default), vertical, horizontal, none |
States
| State | Behavior |
|---|---|
| rest | Default idle, gray border |
| hover | Border darkens |
| active | Border darkens further — while typing |
| focus | Border turns primary blue |
| disabled | Gray bg, muted text, aria-disabled="true" |
| error | Red border |
Accessibility
- Role: native
<textarea>— implicit roletextbox(multiline) - Keyboard: all native textarea keys;
Tabescapes out of the field (no indent behavior) - ARIA:
aria-invalid="true"when in error statearia-disabled="true"when disabledaria-labelrequired for standalone use; otherwise labeled by surrounding FormField- When
maxLengthis set:aria-describedbyshould reference the counter for screen-reader context
- Screen reader: announces label, role "multiline edit", current value; counter read as context when focused near the limit
Do / Don't
Do
- Wrap in FormField for label + helper + error messaging
- Use
maxLengthwithshowCounterfor length-limited content (tweets, SMS, summaries) - Match Textarea size with Input size in the same form column
- Leave
resize='both'in most form contexts — users usually want vertical, occasionally horizontal - Switch to
resize='vertical'in narrow columns where widening would break the layout
Don't
- Don't use Textarea for single-line input — use Input
- Don't hide the counter when
maxLengthis set — users need to know the constraint - Don't disable resize entirely unless the surrounding layout strictly requires a fixed height
- Don't set
maxLengtharbitrarily low — it's a real constraint, not a style choice - Don't override counter / grip positioning via custom CSS — use component props
References
- Figma component:
TextareaComponent Set - Storybook: https://gregnblack.github.io/kanso-protocol/?path=/docs/components-textarea
- Source:
packages/ui/textarea/src/textarea.component.ts - Tokens used:
input/bg,input/fg,input/border,input/placeholder(State collection) — shared with Inputinput/variant-bg,input/variant-border(Input Variant collection)- Counter color:
gray.400; background inherits from host for legibility over text
Changelog
0.1.0— Initial component with 3 sizes, vertical resize, optional counter, CVA integration0.2.0— Replace native resize grip with custom 10×10 SVG grip; thin custom scrollbar (4px + 2px inset)0.3.0— Resize defaults to 'both'; add 'horizontal' option alongside 'vertical' and 'none'