Introduction
Every form-control render (the admin model edit form, the module settings page, and public FormBuilder contact forms) used to be its own hand-rolled if/elseif chain of raw HTML, each with its own slightly different value-casting logic. Zero\Support\Forms unifies all three behind one small set of PHP classes — TextInput, NumberInput, Select, Checkbox, Textarea, and others — each of which both renders itself (via Template::renderFile() against a template in src/Views/components/forms/) and owns its own submitted-value casting, so every caller gets identical, correct behavior for free instead of re-implementing it.
The FormField Contract
namespace Zero\Interfaces;
interface FormField
{
public function __construct(string $name, array $config);
// Reads its own $name key out of $source ($_POST, or a decoded JSON payload) and returns
// the correctly-typed PHP value -- e.g. a checkbox's absence means false, a number is
// clamped to its configured min/max, a select is validated against its options allow-list.
public function castSubmittedValue(array $source);
public function render(): string;
}
Rendering a Field in a View
echo App::makeFormField('number', 'posts_per_page', [
'label' => 'Posts Per Page',
'value' => $currentValue,
'min' => 1,
'max' => 100,
'required' => true,
])->render();
Casting on Submission
A controller collecting POST data does the same thing regardless of which model, module, or form it's handling:
$field = App::makeFormField($fieldConfig['type'], $fieldName, $fieldConfig);
$value = $field->castSubmittedValue($_POST);This is exactly what ModelController, ModuleSettingsController, and FormBuilder's FormApiController each do internally — one shared implementation instead of three that could quietly drift apart.
Registering a Custom Field Type
The type registry follows the same App::registerX() pattern used by blocks and models. Core types are registered once during bootstrap; a module can register its own additional type from inside its own Module::init():
App::registerFormFieldType('sku_lookup', \Zero\Modules\Shop\Support\Forms\SkuLookupField::class);Any class implementing Zero\Interfaces\FormField works — see Per-Module Site Settings for the array-config schema shape a field's constructor receives.