GrTextarea

Пакет: @feugene/granularityядроГруппа: Формы

Берут, когда текст длиннее строки.

Когда брать

  • текст длиннее строки — комментарий, описание, письмо: перенос строк — часть значения;
  • высота подстраиваетсяautosize с maxLines растёт по содержимому и не съедает экран;
  • длина ограничена — счётчик символов и строк рядом с полем;
  • пользователь меняет размер самresize вместо фиксированной высоты.

Когда взять другое

НужноБерите
Значение в одну строкуGrInput
Нужно форматирование текстаrich-text редактора в экосистеме пока нет
Вводится числоGrNumberInput
Длинный текст запрашивают окномGrPromptDialog с multiline

События

СобытиеКогда
update:modelValueна каждый ввод
changeзначение зафиксировано: нативный change (по уходу фокуса) или кнопка очистки
clearзначение стёрто кнопкой очистки (clearable)
focus, blurс объектом FocusEvent

Набор совпадает с GrInput, чтобы обёртки над контролами писались одинаково. Нативные события переизлучаются компонентом: объявленный emit уходит из $attrs, и без этого @change у потребителя перестал бы работать.

clearable повторяет анатомию GrInput: крестик в правом верхнем углу при непустом значении, скрыт при disabled/readonly, настраивается через GrConfigProvider. Обёртка вокруг textarea появляется только под кнопку — без clearable и счётчиков (showCount, showLineCount) поле остаётся корневым элементом, и контракт fallthrough-атрибутов не меняется.

Паритет с `GrInput`

size, readonly, maxlength + showCount работают так же, как в поле ввода: textarea в одной форме рядом с GrInput не должна отличаться ни кеглем, ни набором возможностей.

Счётчик связан с полем через aria-describedby — иначе «12 / 60» видно глазами, но не слышно, при том что ограничение длины и есть его смысл.

Счётчик строк

showLineCount печатает вторую подпись в том же ряду: слева строки, справа символы. Строки считаются логическими — по переводам строки, — поэтому при autosize число не зависит от ширины поля: визуальный перенос строкой не считается.

Подпись локализована и склоняется (gr.textarea.lines), а maxLines меняет её на формат 3 / 10. Ограничения ввода у него нет намеренно: обрезать набранный текст за пользователя компонент не вправе — счётчик показывает перебор (12 / 10), а решение остаётся за формой.

Оба счётчика включаются независимо и оба связаны с полем через aria-describedby.

Автоподгонка высоты

<GrTextarea v-model="text" autosize :rows="2" />

autosize включает директиву v-autosize, которая уже была в пакете и просто не была подключена к компоненту. rows задаёт стартовую высоту.

resize (vertical по умолчанию, none, both) управляет ручным растягиванием: вместе с autosize его обычно выключают.

Тип пропов

GrTextareaProps — объявленный interface, а не typeof props. Прежний экспорт отдавал тип разрешённых пропов: после withDefaults все поля с дефолтами становились обязательными и readonly, и попытка написать const p: GrTextareaProps = { modelValue: '' } падала на ровном месте.

Отключённое состояние

Гасится фоном --gr-muted и текстом --gr-muted-fg, а не opacity: прозрачность разбавляет выверенные на AA токены.

Playground 20

Загружается…

Код
<GrTextarea />

Установка

npm i @feugene/granularity

Импорт

import { GrTextarea } from '@feugene/granularity/components/GrTextarea'

API

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalse
readonlyboolean | undefinedfalseТолько для чтения: значение видно и уходит в форму, но не редактируется.
invalidboolean | undefinedfalse
requiredboolean | undefinedfalseОбязательное поле (`aria-required`). Складывается с `required` у `GrFormField`.
size"xs" | "sm" | "md" | "lg" | undefinedundefined
placeholderstring | undefinedundefined
ariaLabelstring | undefinedundefinedДоступное имя вне `GrFormField`.
clearableboolean | undefinedundefinedКнопка очистки значения. Настраивается через `GrConfigProvider`.
clearLabelstring | undefinedundefinedA11y-подпись кнопки очистки.
namestring | undefinedundefined
idstring | undefinedundefined
rowsnumber | undefined4
state"default" | "success" | "warning" | "danger" | undefined"default"
autocompletestring | undefinedundefined
maxlengthnumber | undefinedundefinedОграничение длины + основа для счётчика символов.
showCountboolean | undefinedfalseПоказывать счётчик символов (`len` или `len/maxlength`).
autosizeboolean | undefinedfalseПодгонять высоту под содержимое (директива `v-autosize`).
resizeGrTextareaResize | undefined"vertical"Ручное изменение размера пользователем.
modelValueобязательныйstring
showLineCountboolean | undefinedПоказывать счётчик строк. Считаются **логические** строки (переводы строки), а не визуальные переносы: при `autosize` число не меняется от ширины поля.
maxLinesnumber | undefinedОриентир по числу строк для счётчика (`3 / 10`). Ввод не ограничивает: обрезать набранный текст за пользователя компонент не вправе.

Events

EventTypeОписание
update:modelValue[value: string]
change[value: string]
clear[]
focus[event: FocusEvent]
blur[event: FocusEvent]

Methods / Expose

Methods / ExposeTypeОписание
focus() => void
blur() => void

Примеры 7

Счётчик строк

Строки считаются логическими — переводами строки, а не визуальными переносами, поэтому при autosize число не зависит от ширины поля. maxLines задаёт ориентир и намеренно не режет набранное.

Счётчик строк слева, символов — справа

3 строки
65 / 240

maxLines задаёт ориентир и не режет набранное

4 / 8

Line Count
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrTextarea } from '@feugene/granularity'

const changelog = ref('fix: чипы автокомплита видно на светлой теме\nfeat: счётчик строк\n')
const script = ref('INT. OFFICE — DAY\n\nОна открывает ноутбук.\n')
</script>

<template>
  <div class="grid gap-4">
    <GrFormField label="Запись в CHANGELOG" hint="Счётчик строк слева, символов — справа">
      <GrTextarea
        v-model="changelog"
        show-line-count
        show-count
        :maxlength="240"
        :rows="4"
      />
    </GrFormField>

    <GrFormField label="Сцена" hint="maxLines задаёт ориентир и не режет набранное">
      <GrTextarea
        v-model="script"
        show-line-count
        :max-lines="8"
        autosize
        :rows="3"
      />
    </GrFormField>
  </div>
</template>

Автовысота и счётчик символов

autosize поверх директивы v-autosize, maxlength + showCount со связкой через aria-describedby.

Высота подстраивается под содержимое

Счётчик связан с полем через aria-describedby

0 / 120

Autosize
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrTextarea } from '@feugene/granularity'

const comment = ref('Поле растёт под текст: директива `v-autosize` была в пакете и просто не была подключена.')
const summary = ref('')
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-2">
    <GrFormField label="Комментарий" hint="Высота подстраивается под содержимое">
      <GrTextarea
        v-model="comment"
        autosize
        resize="none"
        :rows="2"
        placeholder="Что изменилось в релизе"
      />
    </GrFormField>

    <GrFormField label="Краткое описание" hint="Счётчик связан с полем через aria-describedby">
      <GrTextarea
        v-model="summary"
        :maxlength="120"
        show-count
        :rows="3"
        size="sm"
        placeholder="До 120 символов"
      />
    </GrFormField>
  </div>
</template>

Кнопка очистки

clearable добавляет крестик, когда в поле есть текст. Кнопка стоит в таб-порядке сразу за полем, срабатывает на Enter и Space и возвращает фокус в поле — очистка с клавиатуры не выкидывает пользователя из формы.

Крестик появляется, когда есть что стирать

Кнопка очистки достижима `Tab`, срабатывает `Enter` и `Space`

0 / 200

Clearable
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrTextarea } from '@feugene/granularity'

const note = ref('Клиент просил перезвонить после 18:00.\nНапомнить про счёт за март.')
const draft = ref('')
</script>

<template>
  <div class="grid gap-4">
    <GrFormField label="Заметка по клиенту" hint="Крестик появляется, когда есть что стирать">
      <GrTextarea v-model="note" clearable :rows="3" />
    </GrFormField>

    <GrFormField label="Черновик письма" hint="Кнопка очистки достижима `Tab`, срабатывает `Enter` и `Space`">
      <GrTextarea
        v-model="draft"
        clearable
        show-count
        :maxlength="200"
        :rows="3"
        placeholder="Наберите текст — появится счётчик и крестик"
      />
    </GrFormField>
  </div>
</template>

Стартовая и увеличенная высота

Базовый сценарий для short-form и long-form контента: одна и та же textarea может быть компактной или сразу подготовленной под большой объём текста.

Rows
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrTextarea } from '@feugene/granularity'

const shortNote = ref('Release notes highlight the latest API additions.')
const longNote = ref('This textarea starts taller and fits editorial copy, migration notes or incident postmortems.')
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-2">
    <GrFormField label="Default rows">
      <GrTextarea v-model="shortNote" placeholder="Write a short note" />
    </GrFormField>

    <GrFormField label="Expanded rows">
      <GrTextarea v-model="longNote" :rows="8" placeholder="Long-form content" />
    </GrFormField>
  </div>
</template>

Успех и состояния проверки

Показываем state-driven оформление и связку с form-errors без искусственной ручной таблицы API.

Validation toggle

States
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrSwitch, GrTextarea } from '@feugene/granularity'

const draft = ref('Ship the new showcase after validating all public entities.')
const reviewNotes = ref('')
const invalid = ref(false)
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px]">
    <div class="grid gap-3">
      <GrFormField label="Success state">
        <GrTextarea v-model="draft" state="success" />
      </GrFormField>

      <GrFormField label="Validation state" :error="invalid ? 'Review notes are required before publishing' : undefined">
        <GrTextarea
          v-model="reviewNotes"
          placeholder="Add review notes"
          :invalid="invalid"
          :state="invalid ? 'danger' : 'default'"
        />
      </GrFormField>
    </div>

    <div class="grid gap-3 rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
      <div class="text-sm font-semibold text-[var(--gr-fg)]">
        Validation toggle
      </div>
      <GrSwitch v-model="invalid" size="sm">
        Mark review notes as required
      </GrSwitch>
    </div>
  </div>
</template>

Режим просмотра и аудита

Отдельно фиксируем, как GrTextarea выглядит в readonly-like review flow, когда поле временно недоступно для редактирования.

Disabled
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrSwitch, GrTextarea } from '@feugene/granularity'

const disabled = ref(false)
const summary = ref('Subscribers will receive a digest every Monday at 09:00.')
</script>

<template>
  <div class="grid gap-4">
    <div class="flex items-center gap-3">
      <GrSwitch v-model="disabled" size="sm">
        Disable textarea
      </GrSwitch>
    </div>

    <GrFormField label="Operational notes">
      <GrTextarea
        v-model="summary"
        :disabled="disabled"
        :rows="6"
        placeholder="Editable summary"
      />
    </GrFormField>
  </div>
</template>

Шкала размеров и почему она общая

Поле и textarea рядом обязаны читаться одинаково: size у обоих берётся из одной шкалы xs…lg, поэтому форма не рассыпается при смене масштаба.

size="xs"
size="sm"
size="md"
size="lg"

Sizes
<script setup lang="ts">
import { ref } from 'vue'

import { GrFormField, GrInput, GrTextarea } from '@feugene/granularity'

const sizes = ['xs', 'sm', 'md', 'lg'] as const

const title = ref('Weekly digest')
const note = ref('Subscribers receive this summary every Monday at 09:00.')
</script>

<template>
  <div class="grid gap-4">
    <div v-for="size in sizes" :key="size" class="grid gap-2">
      <div class="text-xs font-semibold text-[var(--gr-muted-fg)]">
        size="{{ size }}"
      </div>

      <GrFormField label="Title">
        <GrInput v-model="title" :size="size" />
      </GrFormField>

      <GrFormField label="Note">
        <GrTextarea v-model="note" :size="size" :rows="2" />
      </GrFormField>
    </div>
  </div>
</template>

Доступность

Паттерн APG
Клавиши
Tab из поля — на кнопку очистки при clearable; Enter/Space очищает и возвращает фокус в поле

Полный клавиатурный контракт пакета

Документация компонентаВсе компоненты