Textarea
A multi-line text field — for longer input like messages, descriptions, or notes. It mirrors Input: same v-model binding, same variants, and the same Field integration for labels, descriptions, and error states.
Set the visible height with rows. Like the other controls it's label-less — pair it with a Label, or wrap it in a Field for the full labelled pattern.
Usage
Bind the value with v-model.
<script setup lang="ts">
import { ref } from "vue";
import { Textarea } from "@dlbcodes/ui";
const value = ref("");
</script>
<template>
<div class="w-full max-w-md">
<Textarea v-model="value" placeholder="Write a message…" />
</div>
</template>
Inside a Field
Wrap it in a Field for a label, description, and the accessibility wiring — exactly like Input.
<script setup lang="ts">
import { ref } from "vue";
import {
Textarea,
Field,
FieldLabel,
FieldContent,
FieldDescription,
FieldError,
} from "@dlbcodes/ui";
const bio = ref("");
</script>
<template>
<Field class="w-full max-w-md">
<FieldLabel>Bio</FieldLabel>
<FieldContent>
<Textarea
v-model="bio"
:rows="4"
placeholder="Tell us about yourself…"
/>
<FieldDescription>
A short description for your profile.
</FieldDescription>
</FieldContent>
</Field>
</template>
Error state
Inside a Field, setting invalid applies the error styling to the textarea and wires its aria-describedby to the FieldError — so the problem is announced to screen readers. Standalone, you can set invalid on the textarea directly.
<script setup lang="ts">
import { ref } from "vue";
import {
Textarea,
Field,
FieldLabel,
FieldContent,
FieldDescription,
FieldError,
} from "@dlbcodes/ui";
const feedback = ref("Too short");
</script>
<template>
<Field invalid class="w-full max-w-md">
<FieldLabel>Feedback</FieldLabel>
<FieldContent>
<Textarea
v-model="feedback"
:rows="3"
placeholder="Share your thoughts…"
/>
<FieldDescription>At least 20 characters.</FieldDescription>
<FieldError>Your feedback is too short.</FieldError>
</FieldContent>
</Field>
</template>
Auto-resize
Set autosize and the textarea grows as you type, starting from the rows height and expanding to fit its content. Past a maximum height it stops growing and scrolls instead, so a long entry never takes over the page.
<script setup lang="ts">
import { ref } from "vue";
import { Textarea } from "@dlbcodes/ui";
const value = ref("");
</script>
<template>
<div class="w-full max-w-md">
<Textarea
v-model="value"
autosize
:rows="4"
placeholder="Type several lines — I grow…"
/>
</div>
</template>
Sizes
size takes the same values as Input: sm, base, lg and xl. Unlike an input, a textarea's height comes from rows (and its content, with autosize), so size changes the text size, padding and corners, not the height. Corners follow the theme's --radius.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | number | null | — | The value. Use with v-model. |
rows | number | 4 | Visible height in text rows. Acts as the minimum height when autosize is on. |
autosize | boolean | false | Grows with content from the rows height up to a maximum, then scrolls. |
variant | "primary" | "contrast" | "primary" | Visual style (shares Input's variants). |
size | "sm" | "base" | "lg" | "xl" | "base" | Field size (shares Input's sizes). Sets text size, padding and corners; the height comes from rows. |
placeholder | string | — | Placeholder text. |
disabled | boolean | — | Disables the field. Inherited from a surrounding Field. |
required | boolean | — | Marks required. Inherited from a Field. |
invalid | boolean | — | Applies error styling. Inherited from a Field's error state. |
class | string | — | Classes merged onto the wrapper. |
Accessibility
- Give it a name: pair with a
Labelor wrap in aField. A bare textarea with only a placeholder isn't accessibly labelled. - Inside a
Field,aria-invalid,aria-required, andaria-describedbyare wired automatically.