<script setup lang="ts">
import { ref } from 'vue'
import { Textarea } from '@hina-ui/vue'
const text = ref(
'On my first day at the new school I met a girl on the roof holding an old camera. She said it could photograph tomorrow.',
)
</script>
<template>
<Textarea v-model="text" aria-label="Synopsis" autosize class="w-full max-w-md" />
</template>
Usage
import { Textarea } from '@hina-ui/vue'
The field renders a native textarea with v-model bound to its text. It is three rows tall by default and can be stretched vertically from the corner. Attributes it does not declare land on the textarea, so placeholder, maxlength, name and the like work as usual.
<script setup lang="ts">
import { ref } from 'vue'
import { Textarea } from '@hina-ui/vue'
const text = ref('')
</script>
<template>
<Textarea
v-model="text"
aria-label="Comment"
placeholder="Write a comment"
class="w-full max-w-md"
/>
</template>
Examples
Auto height
With autosize set the height follows the content and the resize handle is hidden. Pass an object to bound the number of rows; past maxRows the field scrolls inside.
<script setup lang="ts">
import { ref } from 'vue'
import { Stack, Textarea } from '@hina-ui/vue'
const free = ref('')
const bounded = ref('')
</script>
<template>
<Stack class="w-full max-w-md">
<Textarea
v-model="free"
aria-label="Unbounded"
autosize
placeholder="Grows with the content, no limit"
/>
<Textarea
v-model="bounded"
aria-label="Two to five rows"
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder="Starts at two rows, stops at five, then scrolls inside"
/>
</Stack>
</template>
Sizes
Three sizes, matching the input. With a single line of content the height equals the input of the same size.
<script setup lang="ts">
import { Stack, Textarea } from '@hina-ui/vue'
</script>
<template>
<Stack class="w-full max-w-md">
<Textarea size="sm" :rows="1" aria-label="Small" placeholder="Small" />
<Textarea size="md" :rows="1" aria-label="Medium" placeholder="Medium" />
<Textarea size="lg" :rows="1" aria-label="Large" placeholder="Large" />
</Stack>
</template>
Variants
primary has a border, background and shadow; secondary uses a tinted background; bare is transparent, without a border, shadow, hover fill or container focus ring. Size and padding keep their existing settings.
bare preserves the disabled state and aria-invalid; FormField can display the error message.
<script setup lang="ts">
import { Textarea, Stack } from '@hina-ui/vue'
</script>
<template>
<Stack class="w-full max-w-md">
<Textarea variant="primary" aria-label="primary" placeholder="primary" />
<Textarea variant="secondary" aria-label="secondary" placeholder="secondary" />
<Textarea variant="bare" aria-label="bare" placeholder="bare" />
</Stack>
</template>
States
invalid marks a failed validation and disabled prevents editing.
<script setup lang="ts">
import { Stack, Textarea } from '@hina-ui/vue'
</script>
<template>
<Stack class="w-full max-w-md">
<Textarea
invalid
aria-label="Invalid"
model-value="The synopsis must be at least twenty characters."
/>
<Textarea
disabled
aria-label="Disabled"
model-value="The synopsis cannot be edited during review."
/>
</Stack>
</template>
In a form
Inside a FormField the label points at the textarea through for, and the description and error message are rendered by the field and linked to it; validation rules and submission belong to the Form.
<script setup lang="ts">
import { reactive, ref } from 'vue'
import * as v from 'valibot'
import { Button, Form, FormField, Text, Textarea } from '@hina-ui/vue'
const schema = v.object({
review: v.pipe(
v.string('Write a review'),
v.trim(),
v.minLength(20, 'At least 20 characters'),
v.maxLength(500, 'At most 500 characters'),
),
})
const values = reactive({ review: '' })
const saved = ref(false)
async function save() {
await new Promise(resolve => setTimeout(resolve, 600))
saved.value = true
}
</script>
<template>
<Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
<FormField name="review" label="Review" description="20 to 500 characters" required>
<Textarea v-model="values.review" />
</FormField>
<Button type="submit" :loading="submitting" class="self-start">Post</Button>
<Text v-if="saved" tone="muted" size="sm">Review posted.</Text>
</Form>
</template>
Behaviour
- Hover, focus, invalid and disabled behave the same as Input.
- Auto height is recomputed on input and on
v-modelchanges; the lower bound defaults torows. - Content beyond the visible rows scrolls inside the field, with the same scrollbar as ScrollArea.
- Clicking blank space inside the field also focuses it.
Accessibility
- Renders a native
textarea, so keyboard and screen reader behaviour comes from the browser. - Pair it with a
labelelement or anaria-labelfor its name. invalidalso setsaria-invalid.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'secondary' | 'bare' | 'primary' | Variant |
size | 'sm' | 'md' | 'lg' | 'md' | Size |
rows | number | 3 | Number of rows |
autosize | boolean | { minRows?: number; maxRows?: number } | false | Whether the height follows the content |
resize | 'none' | 'vertical' | 'vertical' | Whether it can be stretched; off with auto height |
invalid | boolean | false | Whether validation failed |
disabled | boolean | false | Whether the field is disabled |
class | string | — | Classes appended to the root element |
| Event | Payload | Description |
|---|---|---|
update:modelValue | value: string | The text changed |
Exposed methods
input exposes the native HTMLTextAreaElement for reading or setting the selection. focus() focuses it without scrolling the page. Available after mounting on the client.