Fluent form validation for Delphi FMX — rules, presentation, input, and final gate under a single API.
Validator4D is a focused library for form validation in Delphi FMX.
It keeps validation rules, visual feedback, input assistance, and final flow decision explicit, composable, and easy to integrate into existing FMX forms.
The library does not impose an application framework. It is a small, integrable mechanism that fits into any existing screen structure.
In many Delphi FMX applications, form validation gets scattered across event handlers, isolated labels, manual color changes, ad-hoc masks, duplicated messages, and rules that are hard to maintain.
Validator4D exists to solve one specific problem:
bring order to FMX form validation with explicit contracts, configurable presentation, integrated localization, and a single decision point before saving.
It is intentionally focused. It does not try to replace server-side validation, database constraints, domain rules, or become a complete UI framework. It provides a small and predictable mechanism for bringing consistency to form validation built on FMX controls.
Validator4D is organized around a validation core and helper layers by responsibility:
- Core resolves rules, validation lifecycle, and callbacks.
- FMX Presentation controls error labels, title integration, and inline error rendering.
- FMX Input applies masks, keyboard types, and return keys.
- Localization provides embedded languages and JSON overlay loading.
- Internal tracks controls via
FreeNotificationto handle lifetime safely.
For a detailed explanation of the internal flow, see Architecture.md.
- Architecture overview
- Quick overview
- What Validator4D is
- What Validator4D is not
- Four responsibilities, one API
- When to use each presentation technique
- Requirements
- Installation
- Quick start
- Features
- Validation rules
- Error presentation
- Input assistance
- Localization
- Event preservation
- Tab-scoped validation
- Examples
- Tests
- Architecture
- Repository layout
- Design decisions
- Scope and limitations
- Documentation
- Versioning
- License
FValidator := CreateFormValidator
.WithAutoTrim(True)
.WithRequiredTitleMarker(True);
FValidator
.ForControl(EditName)
.Required
.MinLen(2)
.ForControl(EditEmail)
.Required
.Email;
FValidator.AttachOnExit.AttachLive;if not FValidator.ValidateAll then
Exit;
SaveRecord;Rules, visual presentation, input assistance, and callbacks share the same fluent API. The final decision stays centralized in ValidateAll.
Validator4D is a form validation mechanism for Delphi FMX.
It is useful when you need to:
- declare validation rules clearly, close to the controls they validate;
- combine text, date/time, number, mask, selection, and custom rules;
- show error messages with consistent colors and positioning;
- mark required fields automatically on the title labels;
- display messages in multiple languages with embedded catalogs or external JSON;
- preserve existing FMX event handlers on controls;
- validate by tab or by group of controls before final save;
- apply masks, keyboard types, and return keys on input controls;
- centralize the "save / don't save" decision in a single point.
Validator4D is not a complete application framework.
This version does not try to:
- replace database constraints;
- replace server-side validation;
- replace domain or business rules;
- provide a reporting or template engine;
- implement every FMX styling detail;
- guarantee identical visual behavior across all platforms;
- read or manipulate
.fmxfiles at runtime; - replace general-purpose UI tools;
- generate dynamic forms from metadata.
This boundary is intentional. Validator4D focuses on FMX form validation.
Real form validation involves four distinct responsibilities. Validator4D keeps all of them under a coherent fluent API.
Define what makes a control valid or invalid.
FValidator.ForControl(EditEmail)
.Required
.Email;Defines where and how the message appears visually. Three techniques available:
// Technique 1: label created at runtime from the title
TValidatorPresentationFMX.ForControl(EditName)
.CreateErrorLabelFromTitle(LblNameTitle, elpRightOfTitle, 4);
// Technique 2: design-time error label
FValidator.ForControl(EditName)
.WithErrorLabel(LblNameError);
// Technique 3: title as error area
FValidator.ForControl(EditName)
.WithErrorLabelAsTitle(LblNameTitle);Applies mask, keyboard, and return key to controls.
TValidatorInputFMX.ForControl(EditDate)
.Mask('9999-99-99')
.KeyboardTypeNumberPad
.ReturnKeyNext;Decides when to validate and what to do with the result.
if not FValidator.ValidateAll then
Exit;
SaveRecord;The four responsibilities coexist without coupling. You can change the presentation technique without touching the rules, add a mask without changing validation, or change messages (language) without rewriting logic.
| Scenario | Recommended technique |
|---|---|
| You want the library to create the error label automatically | CreateErrorLabelFromTitle |
| You already have an error label in the designer | WithErrorLabel |
| You need to save vertical space | WithErrorLabelAsTitle |
| Messages can be long | CreateErrorLabelFromTitle with elpBelowTitle |
| Layout heavily controlled in the designer | WithErrorLabel |
For a deeper discussion of each technique and when to apply them, see the Practical Guide.
- Delphi 11 or later is the validated target.
- FMX as the UI framework.
- No external runtime dependencies.
Earlier Delphi versions may work, but they have not been officially validated for this initial release.
Clone the repository and add the src folder to your project's Search Path:
Project → Options → Building → Delphi Compiler → Search Path
To use the public API:
uses
Validator4D,
Validator4D.FMX.Input,
Validator4D.FMX.Presentation;For additional localization or language switching:
uses
Validator4D.Localization;The Validator4D.pas unit is the public facade and exposes IFormValidator, IValidationRule, and the CreateFormValidator factory. The other units are helpers organized by responsibility.
FValidator := CreateFormValidator
.WithAutoTrim(True)
.WithRequiredTitleMarker(True);
FValidator
.ForControl(EditName)
.Required
.MinLen(2)
.ForControl(EditEmail)
.Required
.Email;
FValidator.AttachOnExit.AttachLive;// Presentation
TValidatorPresentationFMX.ForControl(EditName)
.CreateErrorLabelFromTitle(LblNameTitle, elpRightOfTitle, 4)
.InlineError(True);
TValidatorPresentationFMX.ForControl(EditEmail)
.CreateErrorLabelFromTitle(LblEmailTitle, elpRightOfTitle, 4)
.InlineError(True);
// Input assistance
TValidatorInputFMX.ForControl(EditEmail)
.KeyboardTypeEmailAddress
.ReturnKeyDone;
// Rules
FValidator
.ForControl(EditName)
.Required
.MinLen(2)
.ForControl(EditEmail)
.Required
.Email;
FValidator.AttachOnExit.AttachLive;
// In a save button
if not FValidator.ValidateAll then
Exit;
SaveRecord;For a complete, ready-to-compile form example, see examples/QuickStart/. For a broad demonstration with nine tabs covering all features, see examples/Showcase/.
| Feature | Available |
|---|---|
| Fluent API | ✓ |
| Text, date/time, number, mask, selection, custom rules | ✓ |
| Presentation in three techniques (auto-created, design-time, title-as-error) | ✓ |
| Automatic required-field marker on titles | ✓ |
| Customizable marker symbol | ✓ |
| Input assistance (mask, keyboard, return key) | ✓ |
| Localization with five embedded languages | ✓ |
| Message loading from JSON as overlay | ✓ |
| Operating-system locale auto-detection | ✓ |
| Live validation (per keystroke) and on-exit | ✓ |
| Preservation of existing FMX event handlers | ✓ |
| Validation of runtime-created controls | ✓ |
| Tab-scoped validation via application iteration | ✓ |
| Per-control and global callbacks | ✓ |
| Customizable colors for valid/invalid states | ✓ |
Automatic MaxLength application from rules |
✓ |
Safe cleanup of destroyed controls via FreeNotification |
✓ |
| Category | Rules |
|---|---|
| Text | Required, WhenFilled, MinLen, MaxLen, ExactLen, Pattern |
| Date/time | DateFormat, TimeFormat, DateTimeFormat, MinDate, MaxDate, BetweenDates, MinTime, MaxTime |
| Number | IntegerOnly, Number, Range |
| Japanese | HiraganaOnly, KatakanaOnly, KanaOnly |
| Internet | Email, URL, IPAddress |
| Mask | Mask |
| Boolean | Checked |
| Selection | RequireSelection, RequireNonFirst, DisallowText, MinSelected, MaxSelected, SelectedInRange |
| Custom | Custom |
WhenFilled is a modifier, not a rule: it causes the chain to be skipped when the control is empty. Useful for optional fields that, when filled, have a defined format.
MinDate, MaxDate, and BetweenDates reject non-empty text that cannot be parsed as a date. MinTime and MaxTime reject non-empty text that cannot be parsed as a time. Use DateFormat, TimeFormat, or DateTimeFormat when you need to enforce an explicit textual format such as yyyy-mm-dd.
Email, URL, and IPAddress are practical regex-based rules covering the most common formats. URL accepts addresses with or without scheme prefix and TLDs up to 6 characters; IPAddress validates IPv4 only. For stricter or specialized formats (IPv6, RFC-compliant URLs, internationalized domains), use the Custom rule with application logic.
Custom is the most important extension point. It allows the application to enforce any business rule without Validator4D needing to know the domain.
For discussion on when to use each rule, see the Practical Guide.
Three configurable techniques, chosen according to the form's layout:
Automatically created label — you provide the title label; Validator4D creates a second label at runtime for the error. Two positions: elpRightOfTitle (compact) or elpBelowTitle (more space).
Design-time error label — you create the label in the .fmx; Validator4D only writes the message into it.
Title as error area — a single TLabel plays two roles: title when valid, title + message when invalid.
The required marker (* by default, customizable) is applied automatically to the title when the control has a required-style rule and Validator4D knows the title label.
FValidator := CreateFormValidator
.WithRequiredTitleMarker(True, '※'); // custom symbolTValidatorInputFMX configures input aspects without mixing with validation rules:
TValidatorInputFMX.ForControl(EditDate)
.Mask('9999-99-99')
.KeyboardTypeNumberPad
.ReturnKeyNext;Keyboard type options: Default, NumberPad, NumbersAndPunctuation, PhonePad, EmailAddress, URL.
Return key options: Default, Next, Done, Go, Search, Send.
Mask placeholders:
| Symbol | Meaning |
|---|---|
9 |
digit |
A |
uppercase letter |
a |
lowercase letter |
X |
alphanumeric character |
* |
any character |
Input assistance complements but does not replace the rules. A mask facilitates typing; the DateFormat rule defines what is accepted.
Validator4D_SetLanguage(vlEN); // English
Validator4D_SetLanguage(vlDE); // German
Validator4D_SetLanguage(vlFR); // French
Validator4D_SetLanguage(vlJA); // Japanese
Validator4D_SetLanguage(vlPT); // PortugueseValidator4D_AutoDetectLanguage;Validator4D_LoadLanguage('validator4d_es.json');The JSON works as a layer over the active catalog. Keys not present in the file continue to come from the base catalog. This allows partial files — you translate only the messages you want to customize.
Validator4D attaches validation bridges to FMX events without overwriting handlers already assigned by the application:
// User's handler, assigned earlier
EditName.OnChange := MySearchHandler;
// Validator4D attaches its bridge without removing the previous handler
FValidator.AttachOnExit.AttachLive;Both handlers run when the event fires. Mask normalization (live) happens before invoking the original handler, so the application sees the already-normalized text.
Detach removes the bridges when needed (for example, during bulk data loading without triggering validation).
Validator4D has no concept of "tab". The application implements that scope by iterating over relevant controls:
function TFrmCustomer.ValidateAddressTab: Boolean;
var
LControl: TControl;
begin
Result := True;
for LControl in [EditZip, EditAddress, EditCity] do
Result := FValidator.Validate(LControl) and Result;
end;The final decision before saving still uses ValidateAll:
if not FValidator.ValidateAll then
Exit;
DataSet.Post;The repository ships two examples under examples/:
Minimal one-form example with two validated fields, intended as a starting point.
examples/QuickStart/
├── project/
│ ├── Validator4D.Quickstart.dpr
│ └── Validator4D.Quickstart.dproj
└── src/
├── Validator4D.Quickstart.Main.pas
└── Validator4D.Quickstart.Main.fmx
Broad reference demo covering most of the mechanism's features, with nine tabs and validation logic split across separate units.
Demonstrates:
- fluent validation rules across nine categories;
- three presentation techniques side by side;
- input assistance (mask, keyboard, return key);
- automatic required marker on titles;
- per-control and global callbacks;
- localization with embedded languages;
- message loading from JSON;
- preservation of existing FMX events;
- tab-scoped validation;
- controls created and destroyed at runtime;
- safe lifetime via
FreeNotification.
The test suite uses DUnitX.
Current coverage includes:
- basic rule behavior;
- custom rules;
- presentation helpers;
- required-title markers;
- regressions in FMX input (masks and keyboard);
- JSON loading and overlay;
- localization and embedded languages;
- lifetime and cleanup of destroyed controls;
- event handler preservation;
- regressions discovered while building the Showcase.
Tests are part of the architecture because they define what cannot regress when internals change.
The project is organized around a validation core and helper layers:
Validator4D.Core
Rules, validators, validation lifecycle, callbacks.
Validator4D.FMX.Input
Masks, keyboard type, return key helpers.
Validator4D.FMX.Presentation
Error labels, title integration, inline error rendering.
Validator4D.Localization
Active language management and JSON overlay loading.
Validator4D.Localization.BuiltIn
Embedded language catalogs (EN, DE, FR, JA, PT).
Validator4D.Internal.ControlRegistry
Control tracking via FreeNotification.
Validator4D.Internal.Rtti
RTTI application on input properties.
Validator4D
Public facade. Exposes IFormValidator, IValidationRule, and CreateFormValidator.
The validation cycle is:
ValidateAll
↓
for each registered control:
resolve rule chain
apply modifiers (AutoTrim, WhenFilled)
execute rules
update visual state via presentation
invoke control's OnValidate (if assigned)
invoke global OnValidated (if assigned)
↓
return Boolean result
For detailed diagrams of each flow, see Architecture.md.
Validator4D/
├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── README_pt-BR.md
│
├── assets/
│ ├── banner.png
│ ├── banner_pt-BR.png
│ └── screenshots/
│ └── showcase-main-window.png
│
├── docs/
│ ├── Architecture.md
│ ├── Architecture_pt-BR.md
│ ├── Guide.md
│ └── Guide_pt-BR.md
│
├── examples/
│ ├── QuickStart/
│ │ ├── project/
│ │ │ ├── Validator4D.Quickstart.dpr
│ │ │ └── Validator4D.Quickstart.dproj
│ │ └── src/
│ │ ├── Validator4D.Quickstart.Main.pas
│ │ └── Validator4D.Quickstart.Main.fmx
│ │
│ └── Showcase/
│ ├── assets/
│ │ └── localization/
│ │ ├── validator4d_custom.json
│ │ └── validator4d_es.json
│ ├── project/
│ │ ├── Validator4D.Showcase.dpr
│ │ └── Validator4D.Showcase.dproj
│ └── src/
│ ├── FormMain.pas
│ ├── FormMain.fmx
│ ├── Validator4D.Showcase.Validation.DateTimeNumber.pas
│ ├── Validator4D.Showcase.Validation.EventsLifetime.pas
│ ├── Validator4D.Showcase.Validation.Internet.pas
│ ├── Validator4D.Showcase.Validation.Localization.pas
│ ├── Validator4D.Showcase.Validation.MasksInput.pas
│ ├── Validator4D.Showcase.Validation.Presentation.pas
│ ├── Validator4D.Showcase.Validation.QuickStart.pas
│ ├── Validator4D.Showcase.Validation.Selection.pas
│ └── Validator4D.Showcase.Validation.TextRules.pas
│
├── src/
│ ├── Validator4D.pas
│ ├── Validator4D.Core.pas
│ ├── Validator4D.FMX.Input.pas
│ ├── Validator4D.FMX.Presentation.pas
│ ├── Validator4D.Localization.pas
│ ├── Validator4D.Localization.BuiltIn.pas
│ ├── Validator4D.Internal.ControlRegistry.pas
│ └── Validator4D.Internal.Rtti.pas
│
└── tests/
├── project/
│ ├── Validator4D.Tests.dpr
│ └── Validator4D.Tests.dproj
└── src/
├── Validator4D.Tests.All.pas
├── Validator4D.Tests.Callbacks.pas
├── Validator4D.Tests.CatalogIntegrity.pas
├── Validator4D.Tests.CustomRules.pas
├── Validator4D.Tests.DateTimeRules.pas
├── Validator4D.Tests.Events.pas
├── Validator4D.Tests.Exceptions.pas
├── Validator4D.Tests.FluentApi.pas
├── Validator4D.Tests.FMX.Input.pas
├── Validator4D.Tests.FMX.InputRegression.pas
├── Validator4D.Tests.FMX.Presentation.pas
├── Validator4D.Tests.Helpers.pas
├── Validator4D.Tests.InternetRules.pas
├── Validator4D.Tests.JapaneseRules.pas
├── Validator4D.Tests.Lifetime.pas
├── Validator4D.Tests.ListBoxSelection.pas
├── Validator4D.Tests.Localization.JsonErrors.pas
├── Validator4D.Tests.Localization.pas
├── Validator4D.Tests.MaskRules.pas
├── Validator4D.Tests.MaxLength.pas
├── Validator4D.Tests.NumberRules.pas
├── Validator4D.Tests.SelectionRules.pas
├── Validator4D.Tests.Smoke.pas
├── Validator4D.Tests.TextRules.pas
└── Validator4D.Tests.ValidateAll.pas
Because a single validator provides a central decision point. Rules can be registered from many helper units, but ownership stays centralized. Splitting a form across multiple IFormValidator instances would fragment the final decision.
Because real forms have different layout needs. Some need automatically created labels; others need design-time labels; others need compact display using the title itself as the error area. Keeping presentation configurable avoids locking in a single visual model.
Because mask, keyboard, and return key help the user type, but they are not the validation contract. A mask restricts format during typing; a rule defines what is accepted as valid. Mixing the two concepts locks in the mechanism's flexibility.
Because partial JSON files are easier to maintain. A project can override only the messages that matter and let the rest come from the active embedded catalog.
Because regional validation rules can grow without bound and may require jurisdiction-specific maintenance. The core provides Pattern for simple formats and Custom for rules with algorithms. Regional packs can be optional and maintained separately.
Because real FMX forms frequently already have events associated with the screen (search, navigation, synchronization). Attaching validation bridges without erasing previous handlers allows Validator4D to be added to existing forms without breaking behavior.
Because controls can be created and destroyed dynamically at runtime. Destroying a control should not break the validator. FreeNotification is the natural FMX mechanism for that observation, and Validator4D uses it across three layers (Core, FMX Input, FMX Presentation).
This version of Validator4D focuses on:
- FMX form validation;
- explicit contracts via fluent API;
- configurable visual presentation;
- integrated localization;
- preservation of existing events;
- safe control lifetime.
It does not intend to:
- replace server-side validation or domain rules;
- replace database constraints;
- implement every FMX styling detail;
- guarantee identical visual behavior across all platforms;
- generate dynamic forms from metadata;
- include jurisdiction-specific regional rules in the core;
- replace complete UI frameworks.
This scope boundary is intentional.
Additional documentation:
The project follows Semantic Versioning.
MIT License — see LICENSE.
Copyright (c) 2026 Eduardo P. Araujo

