Components · Forms & selection
Textarea
Multi-line text input supporting resizable behavior. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.
Preview
A short description for your profile. Grows as you type.
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
Apply the Zetta "Textarea" component to this project using the zetta-design-md skill.
WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `textarea` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `textarea` spec in this project's stack.
AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.
ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility. Anatomy & rules
┌─────────────────────────────────────────────────┐
│ Placeholder or entered text │ ← min-height 80px
│ │
│ │
└─────────────────────────────────────────────────╗┘
↑ resize handle (vertical)
Parts:
- Container — full-width,
{rounded.base}radius,{colors.border-strong}border at rest - Text content — 14px / 400 Geist,
{colors.ink} - Placeholder —
{colors.placeholder}, italic - Resize handle — browser-native, visible at bottom-right when
resize: vertical(default)
| State | Border | Fill | Resize |
|---|---|---|---|
| Default | {colors.border-strong} |
{colors.surface-card} |
vertical |
| Hover | {colors.muted} |
{colors.surface-card} |
vertical |
| Focus | {colors.primary} + glow |
{colors.surface-card} |
vertical |
| Error | {colors.danger} + glow |
{colors.surface-card} |
vertical |
| Disabled | {colors.disabled-border} |
{colors.disabled-bg} |
none |
| Read-only | {colors.hairline} |
{colors.surface-muted} |
none |
When to use
- Multi-line text entry: descriptions, comments, notes, messages, feedback.
- Any field where the user's input may span multiple lines.
When not to use
- Single-line input — use Text Input.
- Structured multi-field data — use multiple single-line inputs instead.
- Rich text with formatting — use a rich text editor component.
Do
- Set a meaningful
rowsvalue that reflects the expected content length. - Allow vertical resize (default) so users can see more of their content.
- Show character count when there is a limit.
Don't
- Don't use
resize: noneunless the layout genuinely cannot accommodate resizing. - Don't use textarea for short single-line answers — use text input.
- Labels: always visible above the textarea.
- Placeholder: short hint — "Describe the issue in detail." or leave blank.
- Character count: show below-right when limit applies — "240 / 500".
- Error: "Description is required." or "Must be under 500 characters."
- Avoid using textarea for structured data — use multiple single-line inputs instead.
Keyboard
| Key | Behavior |
|---|---|
Tab |
Move focus to the textarea |
Shift + Tab |
Move focus away from the textarea |
| Standard text keys | All standard browser text editing behavior |
Tab (inside textarea) |
Inserts a tab character (browser default — override if needed) |
Accessibility
- Always pair with a
<label>viafor/id. - Error state:
aria-invalid="true"andaria-describedby. - Contrast:
{colors.ink}on{colors.surface-card}is 21:1 (AAA).
Spec
Values and token references resolved straight from the Zetta spec.
input-textarea
- backgroundColor
- {colors.surface-card}
- textColor
- {colors.ink}
- borderColor
- {colors.border-strong}
- borderWidth
- 1px
- borderRadius
- {rounded.base}
- fontFamily
- Geist
- fontSize
- 14px
- fontWeight
- 400
- lineHeight
- 1.6
- minHeight
- 80px
- paddingX
- {spacing.sm}
- paddingY
- {spacing.xs}
- width
- 100%
- resize
- vertical
- transition
- border-color {motion.duration-fast}, box-shadow {motion.duration-fast}
input-textarea-placeholder
- textColor
- {colors.placeholder}
input-textarea-hover
- borderColor
- {colors.muted}
input-textarea-focus
- borderColor
- {colors.primary}
- outline
- none
- boxShadow
- 0 0 0 2px {colors.primary-bg}
input-textarea-disabled
- backgroundColor
- {colors.disabled-bg}
- textColor
- {colors.disabled-text}
- borderColor
- {colors.disabled-border}
- cursor
- not-allowed
- resize
- none
input-textarea-error
- borderColor
- {colors.danger}
- boxShadow
- 0 0 0 2px {colors.danger-bg}
input-textarea-read-only
- backgroundColor
- {colors.surface-muted}
- textColor
- {colors.ink}
- borderColor
- {colors.hairline}
- cursor
- default
- resize
- none
input-textarea-resize-none
- resize
- none
input-textarea-resize-horizontal
- resize
- horizontal
input-textarea-resize-both
- resize
- both