@letar/forms

Utility Components

InfoBlock, Divider, and Hidden — structural and utility components for forms

Overview

Utility components help structure forms without adding form fields. They provide visual separation, contextual information, and hidden data storage.

Full example

The complete sandbox example, read directly from the form-develop-app source at build time.

apps/form-develop-app/src/app/utility-demo/page.tsx
'use client'

import { Box, Code, Heading, HStack, Text, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { z } from 'zod/v4'
import { DemoPageLayout } from '../_components'

const DemoSchema = z
  .object({
    firstName: z
      .string()
      .min(1, 'Обязательное')
      .meta({ ui: { title: 'Имя' } }),
    lastName: z
      .string()
      .min(1, 'Обязательное')
      .meta({ ui: { title: 'Фамилия' } }),
    email: z
      .string()
      .email()
      .meta({ ui: { title: 'Email' } }),
    phone: z
      .string()
      .optional()
      .meta({ ui: { title: 'Телефон' } }),
    type: z.enum(['individual', 'company']).meta({
      ui: {
        title: 'Тип клиента',
        options: [
          { value: 'individual', label: 'Физлицо' },
          { value: 'company', label: 'Компания' },
        ],
      },
    }),
    companyName: z
      .string()
      .optional()
      .meta({ ui: { title: 'Название компании' } }),
    inn: z
      .string()
      .optional()
      .meta({ ui: { title: 'ИНН' } }),
    utm_source: z.string().optional(),
    referralCode: z.string().optional(),
  })
  .strip()

export default function UtilityDemoPage() {
  const [submittedData, setSubmittedData] = useState<Record<string, unknown> | null>(null)

  return (
    <DemoPageLayout title="Утилитарные компоненты" description="Form.InfoBlock, Form.Divider, Form.Field.Hidden">
      <VStack gap={6} align="stretch">
        <Form
          debug
          schema={DemoSchema}
          initialValue={{
            firstName: '',
            lastName: '',
            email: '',
            phone: '',
            type: 'individual' as const,
            companyName: '',
            inn: '',
            utm_source: '',
            referralCode: '',
          }}
          onSubmit={(data) => setSubmittedData(data as Record<string, unknown>)}
        >
          {/* InfoBlock — информационный блок */}
          <Heading size="md">Form.InfoBlock</Heading>

          <Form.InfoBlock variant="info" title="Информация">
            Заполните форму для регистрации. Все поля с * обязательны.
          </Form.InfoBlock>

          <Form.InfoBlock variant="warning">Данные нельзя изменить после отправки.</Form.InfoBlock>

          <Form.InfoBlock variant="success" title="Успех">
            Ваша скидка 10% будет применена автоматически.
          </Form.InfoBlock>

          <Form.InfoBlock variant="error">Сервис временно недоступен. Попробуйте позже.</Form.InfoBlock>

          <Form.InfoBlock variant="tip" title="Совет">
            Используйте корпоративный email для быстрой верификации.
          </Form.InfoBlock>

          {/* Divider — разделитель */}
          <Heading size="md" mt={4}>
            Form.Divider
          </Heading>

          <Form.Divider label="Персональные данные" />

          <HStack gap={4}>
            <Form.Field.String name="firstName" />
            <Form.Field.String name="lastName" />
          </HStack>

          <Form.Divider label="Контактные данные" />

          <Form.Field.String name="email" />
          <Form.Field.String name="phone" />

          <Form.Divider />

          <Form.Field.Select name="type" />

          {/* InfoBlock с условным рендерингом */}
          <Form.When field="type" is="company">
            <Form.InfoBlock variant="warning">Для компаний требуется ИНН.</Form.InfoBlock>
            <Form.Field.String name="companyName" />
            <Form.Field.String name="inn" />
          </Form.When>

          <Form.Divider variant="dashed" label="Системные данные" />

          {/* Hidden — скрытые поля */}
          <Heading size="md">Form.Field.Hidden</Heading>
          <Text fontSize="sm" color="fg.muted">
            Скрытые поля не рендерятся в DOM, но передаются при отправке. Проверьте значения в DebugValues внизу.
          </Text>

          <Form.Field.Hidden name="utm_source" value="demo-page" />
          <Form.Field.Hidden name="referralCode" value="PARTNER2026" />

          <Box p={3} bg="bg.subtle" borderRadius="md">
            <Code>Form.Field.Hidden name="utm_source" value="demo-page"</Code>
            <br />
            <Code>Form.Field.Hidden name="referralCode" value="PARTNER2026"</Code>
          </Box>

          <Form.Button.Submit>Отправить</Form.Button.Submit>
        </Form>

        {submittedData && (
          <Box p={4} bg="green.subtle" borderRadius="md">
            <Heading size="sm" mb={2}>
              Отправленные данные:
            </Heading>
            <Code whiteSpace="pre" display="block">
              {JSON.stringify(submittedData, null, 2)}
            </Code>
          </Box>
        )}
      </VStack>
    </DemoPageLayout>
  )
}
ComponentPurpose
Form.InfoBlockInfo/warning/error/success/tip message block
Form.DividerHorizontal separator with optional label
Form.Field.HiddenHidden field (not rendered, only in form state)

Form.InfoBlock

Displays contextual messages inside forms. Based on Chakra UI Alert.

<Form.InfoBlock variant="info" title="Note">
  Fill in all fields to receive a 10% discount.
</Form.InfoBlock>

Variants

VariantColorUse case
info (default)BlueGeneral information
warningOrangeCaution, requirements
errorRedCritical issues
successGreenConfirmations
tipTealHelpful hints

Props

PropTypeDefaultDescription
variant'info' | 'warning' | 'error' | 'success' | 'tip''info'Visual style
titleReactNodeBlock heading
childrenReactNodeContent
appearance'subtle' | 'surface' | 'outline' | 'solid''subtle'Chakra Alert visual variant
size'sm' | 'md' | 'lg''md'Size

Conditional InfoBlock

Combine with Form.When for dynamic messages:

<Form.When field="type" is="company">
  <Form.InfoBlock variant="warning">Company registration requires TIN and legal address.</Form.InfoBlock>
</Form.When>

Form.Divider

Horizontal separator for grouping form sections. Based on Chakra UI Separator.

<Form.Field.String name="firstName" />
<Form.Field.String name="lastName" />

<Form.Divider label="Contact Information" />

<Form.Field.String name="email" />
<Form.Field.String name="phone" />

With icon

import { LuPhone } from 'react-icons/lu'

;<Form.Divider label="Phone Numbers" icon={<LuPhone />} />

Props

PropTypeDefaultDescription
labelReactNodeText label in the middle
iconReactNodeIcon before label
variant'solid' | 'dashed' | 'dotted''solid'Line style
size'xs' | 'sm' | 'md' | 'lg''xs'Line thickness
colorPalettestring'gray'Color palette

Without label

A simple horizontal line:

<Form.Divider />

Hidden

API: Form.Field.Hidden

Stores data in form state without rendering anything in the DOM. Useful for UTM parameters, referral codes, internal IDs.

<Form.Field.Hidden name="utm_source" value={searchParams.get('utm_source')} />
<Form.Field.Hidden name="referralCode" value="PARTNER2026" />

Props

PropTypeDescription
namestringField name in the form
valueunknownValue to set (syncs on change)

Dynamic value

The value is synced reactively — when the value prop changes, the form state updates:

const [geo, setGeo] = useState(null)
useEffect(() => { detectLocation().then(setGeo) }, [])

<Form.Field.Hidden name="location" value={geo} />

Accessing in form submission

Hidden fields are included in the submitted data just like any other field:

<Form
  onSubmit={(data) => {
    console.log(data.utm_source) // "google"
    console.log(data.referralCode) // "PARTNER2026"
  }}
>
  <Form.Field.String name="email" />
  <Form.Field.Hidden name="utm_source" value="google" />
  <Form.Field.Hidden name="referralCode" value="PARTNER2026" />
  <Form.Button.Submit />
</Form>

Complete Example

<Form schema={Schema} initialValue={data} onSubmit={save}>
  <Form.InfoBlock variant="info" title="Registration">
    Please fill in all required fields.
  </Form.InfoBlock>

  <Form.Divider label="Personal Details" />
  <Form.Field.String name="firstName" />
  <Form.Field.String name="lastName" />

  <Form.Divider label="Contact" />
  <Form.Field.String name="email" />
  <Form.Field.String name="phone" />

  <Form.When field="isPremium" is={true}>
    <Form.InfoBlock variant="success">Premium benefits are active!</Form.InfoBlock>
  </Form.When>

  <Form.Field.Hidden name="utm_source" value="landing" />
  <Form.Field.Hidden name="formVersion" value="2.0" />

  <Form.Button.Submit>Register</Form.Button.Submit>
</Form>

Live Example

Try the interactive example on forms-example.letar.best.

On this page