GrFormSection
Берут, когда форма длинная.
Когда брать
- форма длинная — «Контакты», «Доставка», «Оплата»: разделы дают структуру и точки навигации;
- раздел нужен незрячему — заголовок рендерится тегом
h2…h6, и форма обходится по заголовкам; - у раздела есть действия — «Добавить», «Сбросить» в правой части шапки;
- раздел нуждается в пояснении — описание под заголовком, а не подсказка у каждого поля.
Когда взять другое
| Нужно | Берите |
|---|---|
| Правила и отправка | GrForm |
| Обвязка одного поля | GrFormField |
| Разделы переключаются, а не идут подряд | GrTabs |
| Раздел сворачивается | GrCollapse |
| Нужна просто карточка | GrCard |
Заголовок — настоящий
Длинную форму незрячий пользователь обходит по заголовкам, поэтому заголовок
секции рендерится тегом h2…h6, а не жирным текстом:
<GrFormSection title="Профиль" :heading-level="2" description="Публичные данные">
<GrFormField label="Имя"><GrInput v-model="name" /></GrFormField>
</GrFormSection>
headingLevel по умолчанию 3 и читается из GrConfigProvider
(componentDefaults.GrFormSection.headingLevel) — уровень задаётся один раз на
приложение, если у формы своя структура страницы.
Лендмарк по запросу
Корень — <section>, но без имени: именованная секция становится лендмарком
region, и пять секций формы дали бы пять регионов в обзоре диктора. Структуру
несёт заголовок.
Там, где секция действительно крупная и к ней хочется прыгать как к региону,
включите landmark — имя придёт от заголовка:
<GrFormSection landmark title="Безопасность" />
Описание связывается с секцией через aria-describedby в обоих режимах.
Слоты
| Слот | Что заменяет |
|---|---|
title | заголовок целиком — ссылка, бейдж, счётчик |
description | описание целиком |
actions | правая часть шапки: «Добавить», «Сбросить» |
default | содержимое секции |
Шапка — одна строка: заголовок с описанием слева, действия справа; на узком экране действия переносятся под заголовок. Без заголовка, описания и действий шапка не рендерится вовсе.
Рамка и сворачивание
Пропов bordered/collapsible нет намеренно — это композиция, а не настройка:
<GrCard class="p-4">
<GrFormSection title="Профиль" />
</GrCard>
Сворачивание даёт GrCollapse — у него тот же headingLevel,
поэтому структура заголовков не ломается.
Playground 3
Загружается…
<GrFormSection />Установка
npm i @feugene/granularityИмпорт
import { GrFormSection } from '@feugene/granularity/components/GrFormSection'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
title | string | undefined | undefined | — |
description | string | undefined | undefined | — |
headingLevel | 2 | 3 | 4 | 5 | 6 | undefined | undefined | Уровень заголовка под структуру страницы. Не задан — берётся из `GrConfigProvider`, иначе `3`. |
landmark | boolean | undefined | false | Объявить секцию лендмарком `region`. По умолчанию выключено: пять секций формы дали бы пять лендмарков и засорили обзор, а структуру и так несёт заголовок. Включать стоит там, где секция действительно крупная и к ней хочется прыгать как к региону. |
Slots
| Slot | Type | Описание |
|---|---|---|
default | any | Контент секции. |
title | any | Заголовок целиком — вместо строки `title`. |
description | any | Описание целиком — вместо строки `description`. |
actions | any | Действия в правой части шапки: «Добавить», «Сбросить». |
Примеры 5
Уровень заголовка, действия и слоты
Заголовок секции — настоящий h2…h6 (heading-level, по умолчанию h3): именно по заголовкам незрячий пользователь обходит длинную форму. Слот #title собирает заголовок разметкой, #actions кладёт кнопки в правую часть шапки. Лендмарком секция становится только по пропу landmark — иначе пять секций дали бы пять регионов в обзоре.
Участники проекта 2
<script setup lang="ts">
import { ref } from 'vue'
import { GrBadge, GrButton, GrCard, GrFormField, GrFormSection, GrInput } from '@feugene/granularity'
const members = ref([
{ id: 1, email: '[email protected]' },
{ id: 2, email: '[email protected]' },
])
function addMember(): void {
members.value = [...members.value, { id: Date.now(), email: '' }]
}
</script>
<template>
<GrCard class="p-4">
<!-- Заголовок секции — настоящий `h4`, поэтому форма обходится по заголовкам. -->
<GrFormSection
title="Участники проекта"
description="Приглашения уходят на почту сразу после сохранения."
:heading-level="4"
>
<template #title>
Участники проекта
<GrBadge tone="neutral">
{{ members.length }}
</GrBadge>
</template>
<template #actions>
<GrButton variant="outline" size="sm" @click="addMember">
Добавить
</GrButton>
</template>
<div class="grid gap-3">
<GrFormField v-for="(member, index) in members" :key="member.id" :label="`Участник ${index + 1}`">
<GrInput v-model="member.email" type="email" placeholder="[email protected]" />
</GrFormField>
</div>
</GrFormSection>
</GrCard>
</template>Заголовок раздела и поля профиля
Показываем базовую роль GrFormSection: лёгкий heading-wrapper для связанных полей и описания секции.
Team profile
<script setup lang="ts">
import { ref } from 'vue'
import { GrFormField, GrFormSection, GrInput, GrTextarea } from '@feugene/granularity'
const teamName = ref('Platform operations')
const summary = ref('Coordinates deployments, release notes and service health updates.')
</script>
<template>
<GrFormSection
title="Team profile"
description="Use `GrFormSection` when a group of fields needs shared title and supporting copy."
>
<div class="grid gap-4 md:grid-cols-2">
<GrFormField label="Team name" for-id="team-name">
<GrInput id="team-name" v-model="teamName" placeholder="Operations" />
</GrFormField>
<GrFormField label="Summary" for-id="team-summary" class="md:col-span-2">
<GrTextarea id="team-summary" v-model="summary" :rows="4" />
</GrFormField>
</div>
</GrFormSection>
</template>Сгруппированные контролы внутри одной секции
Отдельный пример фиксирует composition-паттерн, где GrFormSection оборачивает и form fields, и более свободные control groups.
Notification routing
<script setup lang="ts">
import { ref } from 'vue'
import { GrCheckbox, GrFormField, GrFormSection, GrInput } from '@feugene/granularity'
const channel = ref('release-updates')
const includeStakeholders = ref(true)
const requireApproval = ref(false)
</script>
<template>
<GrFormSection
title="Notification routing"
description="Section wrappers keep longer forms readable when fields are grouped by intent."
>
<div class="grid gap-4">
<GrFormField label="Slack channel" for-id="notify-channel">
<GrInput id="notify-channel" v-model="channel" placeholder="release-updates" />
</GrFormField>
<div class="grid gap-3 rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-4">
<GrCheckbox v-model="includeStakeholders">
Include business stakeholders in the launch message
</GrCheckbox>
<GrCheckbox v-model="requireApproval">
Require manual approval before notifications are sent
</GrCheckbox>
</div>
</div>
</GrFormSection>
</template>Несколько секций подряд
Такой сценарий показывает, что несколько GrFormSection подряд могут собирать skeleton полноценной settings-страницы без тяжёлой layout-обвязки.
Ownership
Operational assets
<script setup lang="ts">
import { ref } from 'vue'
import { GrBadge, GrFormField, GrFormSection, GrInput } from '@feugene/granularity'
const owner = ref('[email protected]')
const runbook = ref('docs/runbooks/incident-handoff')
</script>
<template>
<div class="grid gap-6">
<GrFormSection title="Ownership">
<GrFormField label="Primary owner" for-id="incident-owner">
<GrInput id="incident-owner" v-model="owner" placeholder="[email protected]" />
</GrFormField>
</GrFormSection>
<GrFormSection title="Operational assets" description="Multiple sections can be stacked to create a light-weight form page skeleton.">
<div class="grid gap-4">
<GrFormField label="Runbook path" for-id="runbook-path">
<GrInput id="runbook-path" v-model="runbook" placeholder="docs/runbooks/..." />
</GrFormField>
<div class="flex flex-wrap gap-2">
<GrBadge tone="success" radius="round">Owner assigned</GrBadge>
<GrBadge tone="info" radius="round">Runbook linked</GrBadge>
</div>
</div>
</GrFormSection>
</div>
</template>Секции с рамкой и скруглением
У GrFormSection один корневой <section>, поэтому class проходит на него насквозь (fallthrough) и мержится с внутренним grid gap-4. Так секцию легко превратить в карточку — прямоугольная рамка (border) или скруглённая (rounded-2xl border) задаётся обычными utility-классами, без пропсов.
Bordered section
Rounded-border section
<script setup lang="ts">
import { ref } from 'vue'
import { GrFormField, GrFormSection, GrInput, GrTextarea } from '@feugene/granularity'
const projectName = ref('Granularity')
const apiToken = ref('')
const notes = ref('Rotate the token every 90 days.')
</script>
<template>
<div class="grid gap-6">
<!-- Прямоугольная рамка: класс проходит на корневой <section> секции. -->
<GrFormSection
title="Bordered section"
description="Border via utility classes — GrFormSection forwards `class` to its root <section>."
class="border border-[var(--gr-brd)] p-5"
>
<GrFormField label="Project name" for-id="bordered-project">
<GrInput id="bordered-project" v-model="projectName" placeholder="Acme" />
</GrFormField>
</GrFormSection>
<!-- Скруглённая рамка: те же классы + rounded-2xl и мягкая подложка. -->
<GrFormSection
title="Rounded-border section"
description="Same wrapper with rounded corners and a card surface."
class="rounded-2xl border border-[var(--gr-brd)] bg-[var(--gr-card)] p-5"
>
<div class="grid gap-4">
<GrFormField label="API token" for-id="rounded-token">
<GrInput id="rounded-token" v-model="apiToken" placeholder="sk_live_…" />
</GrFormField>
<GrFormField label="Notes" for-id="rounded-notes">
<GrTextarea id="rounded-notes" v-model="notes" :rows="3" />
</GrFormField>
</div>
</GrFormSection>
</div>
</template>Внутренний вертикальный ритм секции (grid gap-4) сохраняется — добавляемые классы только расширяют оформление (рамка, паддинги, фон, скругление).