Utilities
@saastro/forms exports utility functions for component detection, Tailwind CSS class generation, test data generation, and field value transforms. All are tree-shakeable — import only what you need.
Component Utilities
These functions help you determine which UI components a form needs and generate install commands for missing ones.
getRequiredComponents(config)
Analyzes a FormConfig and returns all component names needed to render it.
import { getRequiredComponents, FormBuilder } from '@saastro/forms';
const config = FormBuilder.create('contact')
.addField('name', (f) => f.type('text').label('Name').required())
.addField('email', (f) => f.type('email').label('Email').required().email())
.addField('date', (f) => f.type('date').label('Preferred Date').required())
.addStep('main', ['name', 'email', 'date'])
.build();
const needed = getRequiredComponents(config);
// ['Button', 'Field', 'FieldLabel', 'FieldDescription', 'FieldError',
// 'Input', 'Label', 'FormField', 'FormControl',
// 'Calendar', 'Popover', 'PopoverTrigger', 'PopoverContent']
The function always includes core components (Button, Field, FieldLabel, FieldDescription, FieldError) plus any components required by the field types in the config. If a field uses tooltip, tooltip components are included automatically.
getMissingComponents(required, provided)
Compares required components against what’s in your registry.
import { getMissingComponents } from '@saastro/forms';
// Pass your imported UI components as the registry
const missing = getMissingComponents(needed, {
Input,
Button,
Label,
Field,
FieldLabel,
FieldDescription,
FieldError,
FormField,
FormControl,
});
// ['Calendar', 'Popover', 'PopoverTrigger', 'PopoverContent']
getInstallCommand(missing)
Generates a npx shadcn@latest add command for missing components.
import { getInstallCommand } from '@saastro/forms';
const cmd = getInstallCommand(missing);
// "npx shadcn@latest add calendar popover"
Components are grouped by their shadcn package — Calendar maps to calendar, Popover/PopoverTrigger/PopoverContent all map to popover.
groupMissingByPackage(missing)
Groups missing component names by their shadcn package name:
import { groupMissingByPackage } from '@saastro/forms';
const grouped = groupMissingByPackage(missing);
// { calendar: ['Calendar'], popover: ['Popover', 'PopoverTrigger', 'PopoverContent'] }
fieldTypeComponents
The raw mapping of field type to required components:
import { fieldTypeComponents } from '@saastro/forms';
fieldTypeComponents['date'];
// ['Calendar', 'Popover', 'PopoverTrigger', 'PopoverContent', 'Button',
// 'FormField', 'FormControl', 'Field', 'FieldLabel', 'FieldDescription', 'FieldError']
fieldTypeComponents['text'];
// ['Input', 'Label', 'FormField', 'FormControl', 'Field', 'FieldLabel', 'FieldDescription', 'FieldError']
fieldTypeComponents['html'];
// [] — no components needed
coreComponents
Components that every form needs regardless of field types:
import { coreComponents } from '@saastro/forms';
// ['Button', 'Field', 'FieldLabel', 'FieldDescription', 'FieldError']
Tailwind Utilities
These functions generate Tailwind CSS classes for the form grid system. The form runtime uses them internally — useFormLayout wraps getFormGridClass, and the field renderer applies getFieldClass / getHiddenClasses — but all are exported for custom implementations. Reach for them when you render fields outside the standard <Form> component and want your markup to match the form grid.
getFormGridClass(layout?)
Generates the grid container classes and inline styles for the form.
import { getFormGridClass } from '@saastro/forms';
// Default (1 column, gap-4)
getFormGridClass();
// { className: 'grid grid-cols-1 gap-4', style: {} }
// Manual mode with 3 columns
getFormGridClass({ mode: 'manual', columns: 3, gap: 6 });
// { className: 'grid gap-6 grid-cols-3', style: {} }
// Auto mode (uses CSS grid auto-fit)
getFormGridClass({ mode: 'auto', minFieldWidth: 300, columns: 4, gap: 4 });
// {
// className: 'grid gap-4',
// style: {
// gridTemplateColumns: 'repeat(auto-fit, minmax(max(18.75rem, calc((100% - 3rem) / 4)), 1fr))'
// }
// }
In manual mode, it returns a grid-cols-{n} class. In auto mode, it uses inline gridTemplateColumns with auto-fit + minmax() because dynamic column counts can’t be detected by Tailwind at build time. The gap defaults to 4; a gap of 0 adds no gap class.
getFieldClass(formLayout?, fieldLayout?)
Generates responsive col-span-* and order-* classes for a field.
import { getFieldClass } from '@saastro/forms';
// Default
getFieldClass();
// 'col-span-1'
// Responsive columns
getFieldClass({ mode: 'manual', columns: 12 }, { columns: { default: 12, md: 6, lg: 4 } });
// 'col-span-12 md:col-span-6 lg:col-span-4'
// With order
getFieldClass({ mode: 'manual' }, { columns: { default: 6 }, order: { default: 2, lg: 1 } });
// 'col-span-6 order-2 lg:order-1'
// Auto mode — returns empty string (col-span not used with auto-fit)
getFieldClass({ mode: 'auto' }, { columns: { default: 6 } });
// ''
getHiddenClasses(hidden?)
Generates responsive visibility classes.
import { getHiddenClasses } from '@saastro/forms';
getHiddenClasses({ default: 'hidden', md: 'visible' });
// 'hidden md:block'
getHiddenClasses({ lg: 'hidden' });
// 'lg:hidden'
pxToRem(px)
Converts pixels to rem (base 16px). Used internally for auto-mode grid calculations.
import { pxToRem } from '@saastro/forms';
pxToRem(240); // 15
Test Data Generation
Lightweight utilities for generating realistic form data. No external dependencies (no faker.js). Supports locale detection and seeded randomness.
generateTestData(fields, options?)
Generates realistic test values for all fields in a form config, skipping non-input fields (html, button, submit, next, back). Two locales are supported, English and Spanish; the locale is auto-detected from field names and labels (English unless two or more Spanish patterns match) — pass options.locale to force one.
import { generateTestData, FormBuilder } from '@saastro/forms';
const config = FormBuilder.create('contact')
.addField('firstName', (f) => f.type('text').label('First Name').required())
.addField('email', (f) => f.type('email').label('Email').required().email())
.addField('phone', (f) => f.type('tel').label('Phone').optional())
.addField('plan', (f) =>
f
.type('select')
.label('Plan')
.required()
.options([
{ label: 'Basic', value: 'basic' },
{ label: 'Pro', value: 'pro' },
]),
)
.addStep('main', ['firstName', 'email', 'phone', 'plan'])
.build();
const data = generateTestData(config.fields);
// {
// firstName: "James", ← label heuristic
// email: "[email protected]",
// phone: "+1 555 123 4567",
// plan: "pro" ← random option picked
// }
Forms written in Spanish are detected automatically and get Spanish-flavored data:
const data = generateTestData({
nombre: { type: 'text', label: 'Nombre', schema: { required: true } },
correo: { type: 'email', label: 'Correo electrónico', schema: { required: true } },
});
// { nombre: "Carlos", correo: "[email protected]" } ← Spanish locale detected
Options
interface TestDataOptions {
locale?: 'en' | 'es'; // Force locale (default: auto-detect)
seed?: number; // Seed for deterministic output
}
// Deterministic output (same seed = same values)
const data = generateTestData(config.fields, { seed: 42 });
generateFieldValue(name, config, options?)
Generates a realistic value for a single field. Uses a two-layer strategy:
- Label heuristics — For text-like fields (
text,email,tel,url,password,number,input-group,currency), matches the field name/label against known patterns (firstName→"James",email→"[email protected]") - Type fallback — If no pattern matches, generates appropriate data for the field type
When called standalone, the locale defaults to 'en' — auto-detection only happens in generateTestData.
import { generateFieldValue } from '@saastro/forms';
generateFieldValue(
'firstName',
{ type: 'text', label: 'First Name', schema: { required: true } },
{ locale: 'en' },
);
// "James"
generateFieldValue('rating', { type: 'slider', min: 1, max: 5, schema: {} }, { locale: 'en' });
// [3]
generateFieldValue('terms', { type: 'checkbox', label: 'Accept', schema: {} }, { locale: 'en' });
// true
Skips non-input types (html, button, submit, next, back) by returning undefined. file fields also return undefined — File objects can’t be fabricated.
detectLocale(fields)
Auto-detects whether form fields use Spanish or English based on field names and labels.
import { detectLocale } from '@saastro/forms';
detectLocale({
nombre: { type: 'text', label: 'Nombre completo', schema: { required: true } },
correo: { type: 'email', label: 'Correo electrónico', schema: { required: true } },
});
// 'es'
detectLocale({
name: { type: 'text', label: 'Full Name', schema: { required: true } },
email: { type: 'email', label: 'Email', schema: { required: true } },
});
// 'en'
Returns 'es' if 2+ Spanish patterns are found in field names/labels, otherwise 'en'.
Recognized Spanish patterns (24 total, matched as substrings of lowercased field names and labels): nombre, apellido, correo, telefono, teléfono, dirección, direccion, ciudad, provincia, código, codigo, mensaje, empresa, comentario, fecha, edad, género, genero, contraseña, enviar, siguiente, atrás, acepto, términos. Note that only telefono, direccion, codigo, and genero have unaccented variants — the rest must match as written.
Field Transforms
applyFieldTransforms(fields, values)
Applies per-field transform properties to form values before submission. Returns a new object (does not mutate input).
The form runtime applies these transforms automatically before submit actions run — the function is exported so custom submit flows can do the same.
import { applyFieldTransforms } from '@saastro/forms';
const fields = {
email: { type: 'email', label: 'Email', transform: 'lowercase' },
name: { type: 'text', label: 'Name', transform: ['trim', 'uppercase'] },
notes: { type: 'textarea', label: 'Notes' }, // no transform
};
const values = {
email: ' [email protected] ',
name: ' john doe ',
notes: 'Hello world',
};
const result = applyFieldTransforms(fields, values);
// {
// email: " [email protected] ",
// name: "JOHN DOE",
// notes: "Hello world" ← unchanged
// }
Supports three transform formats:
- Single built-in:
'trim' - Chain:
['trim', 'lowercase']— applied left to right - Custom function:
(value) => String(value).trim()
See Field-Level Transforms for the full list of built-in transforms.
Related
- Component System — How component injection works
- Layout System — Grid configuration
- Submit & Actions — Field mapping and transforms
- Types Reference — Full type definitions