GrTextarea
A multi-line field for long text and comments.
Overview
When to take it
- the text is longer than a line — a comment, a description, a letter: line breaks are part of the value;
- the height adjusts —
autosizewithmaxLinesgrows with the content and does not eat up the screen; - the length is limited — a counter of characters and lines beside the field;
- the user changes the size themselves —
resizeinstead of a fixed height.
When to take something else
| Need | Take |
|---|---|
| The value is a single line | GrInput |
| Text formatting is needed | there is no rich-text editor in the ecosystem yet |
| A number is entered | GrNumberInput |
| A long text is asked for with a window | GrPromptDialog with multiline |
Variants every tone and view, rendered by the real component
state
defaultsuccesswarningdanger
Sizes
xssmmdlg
States
normaldisabledreadonlyinvalid
The grid is live: hover and press it. Hover, focus and active states are not drawn here — they belong to the browser, not to a prop.
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrTextarea>Label</GrTextarea>Examples 3 each with a live demo and its code
A reply that is billed by the part
An SMS goes out in parts of 153 characters, and each part is billed, so the
countslot speaks in parts and in what is left of the current one — noshow-countneeded, the slot is the request. The counter stays tied to the field througharia-describedby;maxlengthholds the reply to three parts, andclearableempties the draft in one move, reported withclear.Reply to Ana Ribeiro by SMSReplies go to +351 912 004 118 1 part · 47 left in this partRelease notes for a ten-line panel
autosizegrows the field with the notes so the draft is in sight, andresize="none"drops the handle that would fight it.show-line-countwithmax-linescounts logical lines — the in-app panel cuts at ten — and never cuts the text for the author. Past ten the page setsstate="warning", which adds an icon and a hidden label: colour alone is not a message.Shown in the app after the update, ten lines at most
5 / 10A delivery address and a note
autocomplete="street-address"lets the browser fill the whole address at once, and a postcode it recognises turns the bordersuccessonce the field is left. After pick-up the address isreadonly— selectable and still sent — while the note, a smallersizewith ashow-countlimit, isdisabledand stays out of the form.
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
disabled | boolean | undefined | false | Switch the field off: the value is not edited and does not go into the form. It adds up with `GrFormField`. |
readonly | boolean | undefined | false | Read only: the value is visible and goes into the form but is not edited. |
invalid | boolean | undefined | false | A validation error: a danger border and `aria-invalid`. It adds up with the error of `GrFormField`. |
required | boolean | undefined | false | A mandatory field (`aria-required`). It adds up with the `required` of `GrFormField`. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | The type size and the paddings by the controls scale. `md` by default; taken from `GrConfigProvider`. |
placeholder | string | undefined | undefined | A hint in an empty field. It does not replace the label: the name is given by `GrFormField` or `ariaLabel`. |
ariaLabel | string | undefined | undefined | The accessible name outside `GrFormField`. |
clearable | boolean | undefined | undefined | A button that clears the value. It is configured through `GrConfigProvider`. |
clearLabel | string | undefined | undefined | The a11y label of the clear button. |
name | string | undefined | undefined | The native `name` — the name of the field when the form is submitted. |
id | string | undefined | undefined | The `id` of the field. Not set — taken from `GrFormField`, so that the label finds the field. |
rows | number | undefined | 4 | The height in rows; with `autosize` — the initial and minimum one. `4` by default. |
state | "default" | "success" | "warning" | "danger" | undefined | "default" | The highlight of the border at the developer's choice. `success` and `warning` add an icon and a hidden label; `invalid` is stronger. `default` by default. |
autocomplete | string | undefined | undefined | The native `autocomplete`: what the browser should fill the field with — `street-address`, for instance. |
maxlength | number | undefined | undefined | A limit on the length plus the basis for the counter of characters. |
showCount | boolean | undefined | false | Show the counter of characters (`len` or `len/maxlength`). |
autosize | boolean | undefined | false | Fit the height to the content (the `v-autosize` directive). |
resize | GrTextareaResize | undefined | "vertical" | Manual resizing by the user. |
modelValuerequiredv-model | string | — | The text of the field (`v-model`). |
showLineCount | boolean | undefined | — | Show the counter of lines. The **logical** lines are counted (the line breaks) rather than the visual wraps: with `autosize` the number does not change with the width of the field. |
maxLines | number | undefined | — | A reference number of lines for the counter (`3 / 10`). It does not limit the input: the component has no right to cut the text typed on the user’s behalf. |
Slots
| Slot | Type | Description |
|---|---|---|
count |
| A wording of your own for the character counter instead of `12 / 60` — "48 left", "almost the limit" and anything else. There are two built-in counters, but both are fixed in form, and a second ready-made wording does not replace the first. |
Events
| Event | Signature | Description |
|---|---|---|
update:modelValue | (event: "update:modelValue", value: string): void | — |
change | (event: "change", value: string): void | — |
clear | (event: "clear"): void | — |
focus | (event: "focus", event: FocusEvent): void | — |
blur | (event: "blur", event: FocusEvent): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
focus | () => void | Focus into the field from code. |
blur | () => void | Remove focus from the field. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrTextareaProps, GrTextareaEmits, GrTextareaConfigurableProps } from '@feugene/granularity/components/GrTextarea'Filedist/types/components/GrTextarea/GrTextarea.vue.d.ts
interface GrTextareaProps {
modelValue: string;
placeholder?: string;
autocomplete?: string;
disabled?: boolean;
readonly?: boolean;
invalid?: boolean;
required?: boolean;
ariaLabel?: string;
state?: GrTextareaState;
name?: string;
id?: string;
rows?: number;
size?: GrTextareaSize;
maxlength?: number;
showCount?: boolean;
showLineCount?: boolean;
maxLines?: number;
autosize?: boolean;
clearable?: boolean;
clearLabel?: string;
resize?: GrTextareaResize;
}interface GrTextareaEmits {
(e: 'update:modelValue', value: string): void;
(e: 'change', value: string): void;
(e: 'clear'): void;
(e: 'focus', event: FocusEvent): void;
(e: 'blur', event: FocusEvent): void;
}Filedist/types/components/GrTextarea/config.d.ts
const grTextareaConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrTextarea">;Filedist/types/components/GrTextarea/defaults.d.ts
interface GrTextareaConfigurableProps {
size: GrComponentSize;
clearable: boolean;
}Filedist/types/components/GrTextarea/grTextareaStyles.d.ts
type GrTextareaState = GrControlState;type GrTextareaSize = GrComponentSize;Filedist/types/components/GrTextarea/safelist.d.ts
const grTextareaSafelist: string[];Filedist/types/components/GrTextarea/index.d.ts
type GrTextareaInstance = ComponentExposed<typeof GrTextareaComponent>;Accessibility
APG pattern
—
Full contract of the package: docs/keyboard.md ↗
Usage from the library prose
The events
| Event | When |
|---|---|
update:modelValue | on every keystroke |
change | the value is committed: the native change (on the loss of focus) or the clear button |
clear | the value was erased with the clear button (clearable) |
focus, blur | with a FocusEvent object |
The set matches GrInput, so that wrappers over the controls are written the same
way. The native events are re-emitted by the component: a declared emit leaves $attrs, and without
that the consumer’s @change would stop working.
clearable repeats the anatomy of GrInput: a cross in the upper right corner when the value is
non-empty, hidden with disabled/readonly, configured through GrConfigProvider. The field
keeps room for the cross on the right while it can appear — the first line does not slide under
the ”×”.
The wrapper around the textarea is always there, while the button, the state mark and the
counters (showCount, showLineCount) are its conditional siblings. The wrapper used to appear
together with them, and the <textarea> was re-created: as soon as state became success on
the fly, focus went to <body>, and the following keystrokes were lost. class and style land
on the wrapper, the other attributes on the <textarea> itself (as with GrInput, see
form-controls.md). The field inside is a block: its height is the same
with the siblings and without them.
Parity with GrInput
size, readonly, maxlength + showCount work the same way as in the input field: a textarea in
one form next to a GrInput must differ neither in type size nor in the set of possibilities.
The counter is linked to the field through aria-describedby — otherwise “12 / 60” is seen with the
eyes but not heard, while the limit on the length is its whole point.
state="success" and "warning" get an icon and a hidden label in aria-describedby by the same
device: the colour of the border as the only carrier of meaning is WCAG 1.4.1, and these two states
have no ARIA equivalent of aria-invalid. danger gets no icon — its meaning is already carried
by aria-invalid and the error text from GrFormField — and the mark goes out as soon as the
field is declared invalid.
The mark appears in the same wrapper as the clear button: the field is not re-created in the process, and focus and the caret stay in place.
The line counter
showLineCount prints a second label in the same row: the lines on the left, the characters on the
right. The lines are counted logically — by line breaks — so with autosize the number does not
depend on the width of the field: a visual wrap does not count as a line.
The label is localised and inflected (gr.textarea.lines), and maxLines changes it to the 3 / 10
format. It deliberately has no limit on the input: the component has no right to truncate typed text
on the user’s behalf — the counter shows the excess (12 / 10), and the decision stays with the
form.
Both counters are switched on independently and both are linked to the field through
aria-describedby.
A wording of your own
12 / 60 is not the only way to speak about the length, and the line counter does not replace a
second wording: it is simply a second ready counter, just as fixed in form. The #count slot hands
the text over to the consumer:
<GrTextarea v-model="text" :maxlength="280">
<template #count="{ remaining }">
{{ remaining < 0 ? `${-remaining} over` : `${remaining} left` }}
</template>
</GrTextarea>The slot receives length, maxlength and remaining; without maxlength the last two arrive as
undefined — a limit is not invented. remaining is not clamped to zero: maxlength holds input
from the keyboard, but a value that came into v-model from code steps over the limit, and “-3”
there is more honest than zero.
A separate show-count is not needed for that — a slot that has been given is in itself a request
to show the counter. The place in the row, the link to the field through aria-describedby and the
styling stay the same: the slot changes the text rather than the mechanism.
Automatic height fitting
<GrTextarea v-model="text" autosize :rows="2" />autosize switches on the v-autosize directive, which was already in the package and simply had
not been connected to the component. rows sets the starting height.
resize (vertical by default, none, both) governs manual stretching: together with autosize
it is usually switched off.
The type of the props
GrTextareaProps is a declared interface rather than a typeof props. The previous export gave
away the type of the resolved props: after withDefaults all of the fields with defaults became
required and readonly, and an attempt to write const p: GrTextareaProps = { modelValue: '' }
failed out of the blue.
The disabled state
It is dimmed with the --gr-muted background and --gr-muted-fg text rather than with opacity:
transparency dilutes tokens tuned to AA.
Props in this example