Field Library

Ready-to-use semantic fields — full name, email, phone, consent… — with validators and translations baked in. Define a field once, reuse it everywhere.

View as Markdown

Field Library

FIELD_REGISTRY describes field types (text, email, select…) with type-level defaults. The field library is one level up: a curated set of fully-configured semantic fieldsFull name, Email, Phone, Consent, Country… — each shipping its label, placeholder, serializable validators, and translations. Drop one into a form and you get the config and its translations in a single step, instead of re-wiring “email + required + format” every time.

It lives in its own lean subpath so it never weighs on the root bundle:

import { FIELD_LIBRARY, getFieldLibraryByGroup } from '@saastro/forms/library';
import type { FieldLibraryEntry, FieldLibraryI18n } from '@saastro/forms/library';

What’s in the box

24 fields, grouped for discovery. Each one carries a serializable schema (compiled to Zod at runtime) and a Spanish (es) translation overlay on top of the English base config.

Key Type Group Validators
fullName text personal required, minLength 2
firstName text personal required, minLength 1
lastName text personal required, minLength 1
age number personal required, min 0, max 120, integer
address text personal
country select personal required (12 countries, translated)
birthdate date personal required
postalCode text personal maxLength 12
email email contact required, format email
phone phone contact required
message textarea contact required, maxLength 1000
company text commerce
website url commerce format url
serviceType select commerce required (Design/Dev/Consulting/…)
budgetRange select commerce required (budget brackets)
projectDetails textarea commerce required, minLength 20
consent checkbox survey required, mustBeTrue (terms)
marketingConsent checkbox survey required, mustBeTrue (marketing)
eventAttendance radio survey required (yes/no/maybe, translated)
guestCount number survey min 1, max 10, integer
dietaryNotes textarea survey
dsaRequestType select survey required (GDPR rights, translated)
requestDetails textarea survey
identityDeclaration checkbox survey required, mustBeTrue

An entry’s shape

interface FieldLibraryEntry {
  /** The complete field config — base (English) strings + serializable validators. */
  fieldConfig: FieldConfig;
  /** Display name in the builder palette. */
  label: string;
  /** Optional lucide icon name; defaults to the field type's icon. */
  icon?: string;
  /** Short description for the palette tooltip. */
  description?: string;
  /** Grouping: 'personal' | 'contact' | 'commerce' | 'survey'. */
  group: 'personal' | 'contact' | 'commerce' | 'survey';
  /** Translations keyed by locale. The base config is the default language. */
  i18n?: Record<string, FieldLibraryI18n>;
}

The base config is written in English; i18n.es overlays the Spanish strings (the same shape as a LocaleOverlay field entry — label, placeholder, helperText, description, options). For example:

FIELD_LIBRARY.email = {
  label: 'Email',
  group: 'contact',
  icon: 'Mail',
  description: 'Email address with format validation',
  fieldConfig: {
    type: 'email',
    label: 'Email',
    placeholder: 'you@example.com',
    schema: { required: true, format: 'email' },
  },
  i18n: { es: { label: 'Correo electrónico', placeholder: 'tucorreo@ejemplo.com' } },
};

Using a library field

In a config

Spread the entry’s fieldConfig under whatever name you want. Because schema is a serializable ValidationRules object, the validators come along automatically:

import { FIELD_LIBRARY } from '@saastro/forms/library';
import type { FormConfig } from '@saastro/forms';

const config: FormConfig = {
  formId: 'contact',
  fields: {
    email: FIELD_LIBRARY.email.fieldConfig,
    message: FIELD_LIBRARY.message.fieldConfig,
  },
  steps: { main: { title: 'Contact', fields: ['email', 'message'] } },
  buttons: { submit: { type: 'submit', label: 'Send' } },
};

Bringing the translations along

A library field’s i18n overlay is keyed by locale; re-key it under the field name and merge it into the form’s i18n.translations so the field renders translated when config.locale is set (see Internationalization):

const entry = FIELD_LIBRARY.email;

const config: FormConfig = {
  formId: 'contact',
  fields: { email: entry.fieldConfig },
  steps: { main: { title: 'Contact', fields: ['email'] } },
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es'],
    translations: {
      es: { fields: { email: entry.i18n!.es } },
    },
  },
  locale: 'es', // renders "Correo electrónico"
};

Grouped, for a picker

getFieldLibraryByGroup() returns the entries bucketed by group — handy for rendering a categorized palette:

import { getFieldLibraryByGroup } from '@saastro/forms/library';

const byGroup = getFieldLibraryByGroup();
// { personal: [...], contact: [...], commerce: [...], survey: [...] }

In the visual builder

The form builder surfaces the library in two places:

  • Library section of the field palette — drag Email, Full name, Country… onto a step. The field lands fully configured, and its bundled translation is merged into the form’s i18n automatically (so it shows no “untranslated” ⚠ in that language).
  • Save to library — configure any field, then save it as a reusable entry of your own. Your fields live in the browser (localStorage) and appear in the same Library section under My fields, draggable just like the built-ins.

See also