String Fields
Text input fields — String, Textarea, Password, MaskedInput, RichText, Slug
Full example
The complete sandbox example, read directly from the form-develop-app / form-develop-app-shadcn
source at build time — switch framework/skin to see the same fields across implementations. Both
sources demonstrate String, Textarea, and Password together (form-develop-app's example also
shows other field types in the same file; MaskedInput/RichText/Editable have their own separate
demo files not covered here).
'use client'
import { HStack, VStack } from '@chakra-ui/react'
import { Form } from '@letar/forms'
import { useState } from 'react'
import { z } from 'zod/v4'
import { DemoPageLayout, SubmittedDataPreview } from '../_components'
/**
* Demo schema with all field types
*/
const DemoSchema = z.object({
// String field
name: z
.string()
.min(2)
.meta({
ui: { title: 'Name', placeholder: 'Enter your name' },
}),
// Textarea field
description: z.string().meta({
ui: { title: 'Description', placeholder: 'Enter description...' },
}),
// Number field
age: z
.number()
.min(0)
.max(150)
.meta({
ui: { title: 'Age' },
}),
// Date field
birthDate: z.string().meta({
ui: { title: 'Birth Date' },
}),
// Time field
wakeUpTime: z.string().meta({
ui: { title: 'Wake Up Time' },
}),
// Password field
password: z
.string()
.min(6)
.meta({
ui: { title: 'Password', placeholder: 'Enter password' },
}),
// Select field (Chakra styled, required)
framework: z.string().meta({
ui: { title: 'Framework', placeholder: 'Select framework' },
}),
// Select field (optional - will show clear button)
theme: z
.string()
.optional()
.meta({
ui: { title: 'Theme', placeholder: 'Select theme' },
}),
// Combobox field (static options with search)
country: z.string().meta({
ui: { title: 'Country', placeholder: 'Search country...' },
}),
// Combobox with groups
language: z
.string()
.optional()
.meta({
ui: { title: 'Programming Language', placeholder: 'Search language...' },
}),
// Listbox field (single selection)
role: z.string().meta({
ui: { title: 'Role', description: 'Select your role' },
}),
// Listbox field (multiple selection)
skills: z
.array(z.string())
.optional()
.meta({
ui: { title: 'Skills', description: 'Select all that apply' },
}),
// Checkbox field
newsletter: z.boolean().meta({
ui: { title: 'Subscribe to newsletter' },
}),
// Switch field
darkMode: z.boolean().meta({
ui: { title: 'Dark Mode' },
}),
// RadioGroup field
size: z.string().meta({
ui: { title: 'Size', description: 'Choose your preferred size' },
}),
// RadioGroup horizontal
priority: z.string().meta({
ui: { title: 'Priority' },
}),
// RadioCard field (card-based single selection)
plan: z.string().meta({
ui: { title: 'Subscription Plan', description: 'Choose your plan' },
}),
// CheckboxCard field (card-based multiple selection)
features: z
.array(z.string())
.optional()
.meta({
ui: { title: 'Features', description: 'Select desired features' },
}),
// SegmentedGroup field (segmented control)
billing: z.string().meta({
ui: { title: 'Billing Cycle' },
}),
// SegmentedGroup small
displayMode: z.string().meta({
ui: { title: 'Display Mode' },
}),
// ColorPicker field
brandColor: z.string().meta({
ui: { title: 'Brand Color', description: 'Select your brand color' },
}),
// ColorPicker with minimal UI (swatches only)
accentColor: z.string().meta({
ui: { title: 'Accent Color' },
}),
// Editable field (inline editing)
editableTitle: z.string().meta({
ui: { title: 'Editable Title' },
}),
// Editable multiline
editableNotes: z.string().meta({
ui: { title: 'Notes', description: 'Click to add notes' },
}),
// Schedule field (weekly working hours)
workingHours: z.any().meta({
ui: { title: 'Working Hours', description: 'Set your availability' },
}),
})
type DemoFormData = z.infer<typeof DemoSchema>
const frameworkOptions = [
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Angular', value: 'angular' },
{ label: 'Svelte', value: 'svelte' },
]
const themeOptions = [
{ label: 'Light', value: 'light' },
{ label: 'Dark', value: 'dark' },
{ label: 'System', value: 'system' },
]
const sizeOptions = [
{ label: 'Small', value: 'sm' },
{ label: 'Medium', value: 'md' },
{ label: 'Large', value: 'lg' },
]
const priorityOptions = [
{ label: 'Low', value: 'low' },
{ label: 'Medium', value: 'medium' },
{ label: 'High', value: 'high' },
]
const countryOptions = [
{ label: 'Russia', value: 'ru' },
{ label: 'United States', value: 'us' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
{ label: 'Japan', value: 'jp' },
{ label: 'China', value: 'cn' },
{ label: 'Brazil', value: 'br' },
{ label: 'India', value: 'in' },
]
const languageOptions = [
{ label: 'TypeScript', value: 'ts', group: 'Frontend' },
{ label: 'JavaScript', value: 'js', group: 'Frontend' },
{ label: 'React', value: 'react', group: 'Frontend' },
{ label: 'Python', value: 'python', group: 'Backend' },
{ label: 'Go', value: 'go', group: 'Backend' },
{ label: 'Rust', value: 'rust', group: 'Backend' },
{ label: 'PostgreSQL', value: 'postgres', group: 'Database' },
{ label: 'MongoDB', value: 'mongo', group: 'Database' },
]
const roleOptions = [
{ label: 'Developer', value: 'developer' },
{ label: 'Designer', value: 'designer' },
{ label: 'Manager', value: 'manager' },
{ label: 'QA Engineer', value: 'qa' },
]
const skillOptions = [
{ label: 'Frontend', value: 'frontend' },
{ label: 'Backend', value: 'backend' },
{ label: 'DevOps', value: 'devops' },
{ label: 'Mobile', value: 'mobile' },
{ label: 'Data Science', value: 'data' },
]
const planOptions = [
{ label: 'Free', value: 'free', description: 'Basic features for personal use' },
{ label: 'Pro', value: 'pro', description: 'All features for professionals' },
{ label: 'Enterprise', value: 'enterprise', description: 'Custom solutions for teams' },
]
const featureOptions = [
{ label: 'Analytics', value: 'analytics', description: 'Track usage metrics' },
{ label: 'API Access', value: 'api', description: 'Programmatic access' },
{ label: 'Priority Support', value: 'support', description: '24/7 support' },
{ label: 'Custom Branding', value: 'branding', description: 'White-label solution' },
]
const billingOptions = [
{ label: 'Monthly', value: 'monthly' },
{ label: 'Quarterly', value: 'quarterly' },
{ label: 'Yearly', value: 'yearly' },
]
const displayModeOptions = [
{ label: 'List', value: 'list' },
{ label: 'Grid', value: 'grid' },
{ label: 'Table', value: 'table' },
]
const initialValues: DemoFormData = {
name: '',
description: '',
age: 0,
birthDate: '',
wakeUpTime: '',
password: '',
framework: '',
theme: undefined,
// Непустой initialValue нарочно — регресс-проверка на баг «Combobox с начальным значением
// рендерится пустым» (libs/forms CHANGELOG 2.11.5): при открытии страница должна сразу
// показывать "United States" в поле, а не пустой инпут с плейсхолдером.
country: 'us',
language: undefined,
role: '',
skills: undefined,
newsletter: false,
darkMode: false,
size: '',
priority: '',
plan: '',
features: undefined,
billing: '',
displayMode: '',
brandColor: '#4299E1',
accentColor: '#38B2AC',
editableTitle: 'Click to edit this title',
editableNotes: '',
workingHours: {
monday: { open: '09:00', close: '18:00' },
tuesday: { open: '09:00', close: '18:00' },
wednesday: { open: '09:00', close: '18:00' },
thursday: { open: '09:00', close: '18:00' },
friday: { open: '09:00', close: '17:00' },
saturday: null,
sunday: null,
},
}
export default function FieldsDemoPage() {
const [submittedData, setSubmittedData] = useState<DemoFormData | null>(null)
const handleSubmit = (data: DemoFormData) => {
setSubmittedData(data)
}
return (
<DemoPageLayout title="Fields Demo" description="Демонстрация всех типов полей">
<Form initialValue={initialValues} schema={DemoSchema} onSubmit={handleSubmit}>
<VStack gap={4} align="stretch">
{/* String field */}
<Form.Field.String name="name" />
{/* Textarea field */}
<Form.Field.Textarea name="description" rows={4} />
{/* Number field */}
<Form.Field.Number name="age" min={0} max={150} />
{/* Date field */}
<Form.Field.Date name="birthDate" />
{/* Time field */}
<Form.Field.Time name="wakeUpTime" />
{/* Password field */}
<Form.Field.Password name="password" />
{/* Select field (Chakra styled, required - no clear button) */}
<Form.Field.Select name="framework" options={frameworkOptions} />
{/* Select field (optional - shows clear button automatically) */}
<Form.Field.Select name="theme" options={themeOptions} />
{/* Combobox field (searchable select) */}
<Form.Field.Combobox name="country" options={countryOptions} />
{/* Combobox with groups */}
<Form.Field.Combobox name="language" options={languageOptions} />
{/* Listbox field (single selection) */}
<Form.Field.Listbox name="role" options={roleOptions} />
{/* Listbox field (multiple selection) */}
<Form.Field.Listbox name="skills" options={skillOptions} selectionMode="multiple" />
{/* RadioGroup field (vertical) */}
<Form.Field.RadioGroup name="size" options={sizeOptions} />
{/* RadioGroup field (horizontal) */}
<Form.Field.RadioGroup name="priority" options={priorityOptions} orientation="horizontal" />
{/* RadioCard field (card-based single selection) */}
<Form.Field.RadioCard name="plan" options={planOptions} />
{/* CheckboxCard field (card-based multiple selection) */}
<Form.Field.CheckboxCard name="features" options={featureOptions} />
{/* SegmentedGroup field */}
<Form.Field.SegmentedGroup name="billing" options={billingOptions} />
{/* SegmentedGroup field (small size) */}
<Form.Field.SegmentedGroup name="displayMode" options={displayModeOptions} size="sm" />
{/* ColorPicker field */}
<Form.Field.ColorPicker name="brandColor" />
{/* ColorPicker with minimal UI (swatches only) */}
<Form.Field.ColorPicker
name="accentColor"
showArea={false}
showSliders={false}
showEyeDropper={false}
swatches={['#FF0000', '#00FF00', '#0000FF', '#38B2AC', '#9F7AEA', '#ED64A6']}
/>
{/* Editable field (inline editing) */}
<Form.Field.Editable name="editableTitle" showControls />
{/* Editable multiline */}
<Form.Field.Editable name="editableNotes" multiline placeholder="Click to add notes..." />
{/* Schedule field (weekly working hours) */}
<Form.Field.Schedule name="workingHours" />
<HStack gap={4}>
{/* Checkbox field */}
<Form.Field.Checkbox name="newsletter" />
{/* Switch field */}
<Form.Field.Switch name="darkMode" />
</HStack>
<HStack gap={4}>
<Form.Button.Submit>Submit</Form.Button.Submit>
<Form.Button.Reset>Reset</Form.Button.Reset>
</HStack>
{/* JSON-инспектор значений формы */}
<Form.DebugValues />
</VStack>
</Form>
<SubmittedDataPreview data={submittedData} />
</DemoPageLayout>
)
}'use client'
import { FieldCheckbox, FieldNumber, FieldPassword, FieldString, FieldSwitch, FieldTextarea } from '@letar/forms-shadcn'
import { useState } from 'react'
import { DemoForm, DemoPageLayout, SubmittedDataPreview } from '../_components'
interface BasicFieldsValues {
name: string
bio: string
portions: number
password: string
agree: boolean
notifications: boolean
}
const defaultValues: BasicFieldsValues = {
name: '',
bio: '',
portions: 1,
password: '',
agree: false,
notifications: true,
}
export default function BasicFieldsDemoPage() {
const [submitted, setSubmitted] = useState<BasicFieldsValues | null>(null)
return (
<DemoPageLayout
title="Базовые поля"
description="String, Textarea, Number, Password, Checkbox, Switch"
>
<DemoForm<BasicFieldsValues> defaultValues={defaultValues} onSubmit={setSubmitted}>
<FieldString name="name" label="Имя" placeholder="Введите имя" required />
<FieldTextarea name="bio" label="О себе" rows={3} />
<FieldNumber name="portions" label="Порции" min={1} max={10} step={1} />
<FieldPassword name="password" label="Пароль" />
<FieldCheckbox name="agree" label="Согласен с условиями" />
<FieldSwitch name="notifications" label="Уведомления" />
<button
type="submit"
className="bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm font-medium"
>
Отправить
</button>
</DemoForm>
<SubmittedDataPreview data={submitted} />
</DemoPageLayout>
)
}import { AppForm } from '@letar/forms-vue'
import { defineComponent, h, ref } from 'vue'
import { z } from 'zod'
import { FieldString } from '../../src/index'
/**
* Изолированный пример FieldString — самодостаточный файл (своя Zod-схема, свой AppForm),
* читается с диска отдельно от остальных примеров (form-docs, Этап 1 P7).
*/
const schema = z.object({
title: z.string().min(3, 'Минимум 3 символа').meta({ ui: { title: 'Название', placeholder: 'Введите название' } }),
})
export const StringDemo = defineComponent({
name: 'StringDemo',
setup() {
const submitted = ref<Record<string, unknown> | null>(null)
return () =>
h('div', { class: 'space-y-6' }, [
h(
AppForm,
{
schema,
initialValue: { title: '' },
onSubmit: (value: Record<string, unknown>) => {
submitted.value = value
},
},
{
default: () => [
h(FieldString, { name: 'title' }),
h(
'button',
{ type: 'submit', class: 'bg-primary text-primary-foreground rounded-md px-4 py-2 text-sm' },
'Сохранить',
),
],
},
),
submitted.value
? h('pre', { class: 'bg-muted mt-4 rounded-md p-3 text-xs' }, JSON.stringify(submitted.value, null, 2))
: null,
])
},
})Angular-пример для этого поля появится позже.
String
API: Form.Field.String
Standard single-line text input. The most common field type.
const Schema = z.object({
name: z.string().min(2).max(100).meta({
ui: { title: 'Full Name', placeholder: 'Enter your name' },
}),
})
<Form.Field.String name="name" />Auto Constraints
Zod constraints are automatically applied to the input:
z.string().min(2).max(100)
// → minLength={2} maxLength={100} on the <input>
z.string().email()
// → type="email"
z.string().url()
// → type="url"Textarea
API: Form.Field.Textarea
Multi-line text input for longer content.
const Schema = z.object({
description: z.string().min(10).max(2000).meta({
ui: { title: 'Description', placeholder: 'Describe your project...' },
}),
})
<Form.Field.Textarea name="description" />Password
API: Form.Field.Password
Password input with a visibility toggle button.
const Schema = z.object({
password: z.string().min(8).meta({
ui: { title: 'Password' },
}),
})
<Form.Field.Password name="password" />MaskedInput
API: Form.Field.MaskedInput
Input with format mask for structured data. Requires use-mask-input peer dependency.
The mask is set via the component's own mask prop, not through ui in Zod meta() — there is
no mask key in FieldUIMeta.
const Schema = z.object({
phone: z.string().meta({
ui: { title: 'Phone' },
}),
creditCard: z.string().meta({
ui: { title: 'Card Number' },
}),
})
<Form.Field.MaskedInput name="phone" mask="+1 (999) 999-9999" />
<Form.Field.MaskedInput name="creditCard" mask="9999 9999 9999 9999" />RichText
API: Form.Field.RichText
Rich text editor for HTML/markdown content.
const Schema = z.object({
content: z.string().meta({
ui: { title: 'Article Content' },
}),
})
<Form.Field.RichText name="content" />Requires @tiptap/react and @tiptap/starter-kit as peer dependencies.
Slug
API: Form.Field.Slug
A text field that mirrors a URL slug derived from a sibling field (usually a title) — until the user edits the slug by hand, after which mirroring stops permanently for that form session.
const Schema = z.object({
name: z.string().meta({ ui: { title: 'Name' } }),
slug: z.string().meta({ ui: { title: 'URL' } }),
})
<Form.Field.String name="name" />
<Form.Field.Slug name="slug" source="name" />On an edit form (the slug already has a non-empty value on mount — the record has a published
address), auto-mirroring defaults to off, since silently changing a URL that external links
already point to would break them. Opt in with syncOnEdit:
<Form.Field.Slug name="slug" source="name" syncOnEdit={!isPublished} />Props: source (required — sibling field name in the same group), syncOnEdit (default
false), slugify (custom transliteration function, defaults to slugify() from
@letar/format-utils, ГОСТ 7.79-2000 Cyrillic transliteration), maxLength, autoComplete.
Editable
API: Form.Field.Editable
Inline editing — click text to edit, press Enter or blur to save.
const Schema = z.object({
nickname: z.string().meta({
ui: { title: 'Nickname' },
}),
})
<Form.Field.Editable name="nickname" />