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.

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.

KeyTypeGroupValidators
fullNametextpersonalrequired, minLength 2
firstNametextpersonalrequired, minLength 1
lastNametextpersonalrequired, minLength 1
agenumberpersonalrequired, min 0, max 120, integer
addresstextpersonal
countryselectpersonalrequired (12 countries, translated)
birthdatedatepersonalrequired
postalCodetextpersonalmaxLength 12
emailemailcontactrequired, format email
phonephonecontactrequired
messagetextareacontactrequired, maxLength 1000
companytextcommerce
websiteurlcommerceformat url
serviceTypeselectcommercerequired (Design/Dev/Consulting/…)
budgetRangeselectcommercerequired (budget brackets)
projectDetailstextareacommercerequired, minLength 20
consentcheckboxsurveyrequired, mustBeTrue (terms)
marketingConsentcheckboxsurveyrequired, mustBeTrue (marketing)
eventAttendanceradiosurveyrequired (yes/no/maybe, translated)
guestCountnumbersurveymin 1, max 10, integer
dietaryNotestextareasurvey
dsaRequestTypeselectsurveyrequired (GDPR rights, translated)
requestDetailstextareasurvey
identityDeclarationcheckboxsurveyrequired, 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: '[email protected]',
    schema: { required: true, format: 'email' },
  },
  i18n: { es: { label: 'Correo electrónico', placeholder: '[email protected]' } },
};

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