PHPackages                             joby/smol-form - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. joby/smol-form

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

joby/smol-form
==============

A lightweight and straightforward form builder with fluent interfaces and flexible submission handling.

v1.0.0-beta1(2mo ago)03MITPHPPHP &gt;=8.3CI passing

Since Feb 26Pushed 2mo agoCompare

[ Source](https://github.com/joby-lol/smol-form)[ Packagist](https://packagist.org/packages/joby/smol-form)[ RSS](/packages/joby-smol-form/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (5)Versions (2)Used By (0)

smolForm
========

[](#smolform)

A lightweight, composable form builder for PHP. Forms work without JavaScript and are usable without CSS. Any JavaScript or CSS you add is genuine progressive enhancement — never load-bearing.

Installation
------------

[](#installation)

```
composer require joby/smol-form
```

PHP Version
-----------

[](#php-version)

Requires PHP 8.3+.

About
-----

[](#about)

smolForm provides typed, validated form inputs with a fluent API. Each input is an HTML element that can be cast to a string, CSRF protection is built in via session, and validation runs server-side with errors surfaced inline.

- **No required JavaScript**: Forms submit and validate without a single line of JS
- **Typed values**: Each input returns a typed PHP value (`string`, `float`, `DateTimeImmutable`, `bool`, `array`)
- **Server-side validation**: Built-in rules per input type, plus custom rules on the Validator
- **Fluent API**: Chainable configuration methods on all inputs
- **CSRF protection**: Built in via smol-session
- **Disableable inputs**: Disabled inputs return null regardless of submitted data

Basic Usage
-----------

[](#basic-usage)

```
use Joby\Smol\Form\Form;
use Joby\Smol\Form\Field;
use Joby\Smol\Form\Inputs\TextInput;
use Joby\Smol\Form\Inputs\EmailInput;

$form = new Form('contact_form');

$name = new TextInput('name', 'Your name');
$email = new EmailInput('email', 'Email address');
$email->formValidator()->setRequired(true);

$form->addChild(new Field($name));
$form->addChild(new Field($email));
$form->finalize();

echo $form;

if ($form->isFormAttempted() && $form->isValid()) {
    $name_value = $name->formValue();   // string|null
    $email_value = $email->formValue(); // string|null
}
```

Inputs
------

[](#inputs)

All inputs are constructed with at minimum a field name and a label. Most accept an optional default value.

### Text inputs

[](#text-inputs)

```
use Joby\Smol\Form\Inputs\TextInput;
use Joby\Smol\Form\Inputs\TextareaInput;
use Joby\Smol\Form\Inputs\EmailInput;
use Joby\Smol\Form\Inputs\PasswordInput;
use Joby\Smol\Form\Inputs\UrlInput;
use Joby\Smol\Form\Inputs\TelInput;
use Joby\Smol\Form\Inputs\SearchInput;

$text     = new TextInput('username', 'Username');
$textarea = new TextareaInput('bio', 'Biography');
$email    = new EmailInput('email', 'Email address');     // validates format
$password = new PasswordInput('password', 'Password');   // never pre-filled
$url      = new UrlInput('website', 'Website');           // validates format, defaults placeholder to https://
$tel      = new TelInput('phone', 'Phone number');        // no format validation — phone formats vary too much internationally
$search   = new SearchInput('q', 'Search');

// Placeholder text (note: bad practice to use instead of a label)
$text->setPlaceholder('e.g. johndoe');
```

`formValue()` returns `string|null` for all text inputs.

### Number input

[](#number-input)

```
use Joby\Smol\Form\Inputs\NumberInput;

$number = new NumberInput('quantity', 'Quantity');
$number->setNumberMin(1.0);
$number->setNumberMax(100.0);
$number->setNumberStep(5.0);
```

`formValue()` returns `float|null`. Min, max, and step constraints are validated server-side and also set as HTML attributes for browser-level enforcement. Step is validated relative to the field's default value if one is set, otherwise from zero.

### Date input

[](#date-input)

```
use Joby\Smol\Form\Inputs\DateInput;

$date = new DateInput('birthday', 'Date of birth');
$date->setDateMin(new DateTimeImmutable('1900-01-01'));
$date->setDateMax(new DateTimeImmutable('today'));
```

`formValue()` returns `DateTimeImmutable|null` with time zeroed to midnight. Min and max accept any `DateTimeInterface`.

### DateTime input

[](#datetime-input)

```
use Joby\Smol\Form\Inputs\DateTimeInput;

$dt = new DateTimeInput('scheduled_at', 'Schedule for');
$dt->setDateTimeMin(new DateTimeImmutable('now'));
$dt->setDateTimeMax(new DateTimeImmutable('+1 year'));

// Step accepts integer seconds or a DateInterval
// Note: months and years are not supported in DateInterval step (variable length)
$dt->setDateTimeStep(1800);                    // 30 minutes in seconds
$dt->setDateTimeStep(new DateInterval('PT1H')); // 1 hour as DateInterval

// Step validation uses the field's default as base — if no default is set, step is not validated
$dt->setFormDefault(new DateTimeImmutable('2025-01-01 09:00:00'));
```

`formValue()` returns `DateTimeImmutable|null`.

### Checkbox

[](#checkbox)

```
use Joby\Smol\Form\Inputs\CheckboxInput;

$agree = new CheckboxInput('agree', 'I agree to the terms', default: false);
```

`formValue()` returns `bool`. Note that unchecked checkboxes submit no value — smolForm handles this correctly.

### Radio buttons

[](#radio-buttons)

```
use Joby\Smol\Form\Inputs\RadioInput;

$size = new RadioInput('size', 'Size', [
    'sm' => 'Small',
    'md' => 'Medium',
    'lg' => 'Large',
], default: 'md');
```

`formValue()` returns `string|null`. Only valid option keys are returned — submitted values not in the options array are rejected.

### Select

[](#select)

```
use Joby\Smol\Form\Inputs\SelectInput;

$country = new SelectInput('country', 'Country', [
    'us' => 'United States',
    'ca' => 'Canada',
    'gb' => 'United Kingdom',
]);

// Optional empty/placeholder option
$country->setEmptyOption('-- select a country --');
```

`formValue()` returns `string|null`. Submitted values not in the options array are rejected.

### Multi-checkbox

[](#multi-checkbox)

```
use Joby\Smol\Form\Inputs\MultiCheckboxInput;

$tags = new MultiCheckboxInput('tags', 'Tags', [
    'php'        => 'PHP',
    'javascript' => 'JavaScript',
    'css'        => 'CSS',
], default: ['php']);

// Optional selection count constraints
$tags->setFormMinSelections(1);
$tags->setFormMaxSelections(3);
```

`formValue()` returns `array` of selected keys (empty array if none selected, null if form not yet attempted). Only valid option keys are included.

Validation
----------

[](#validation)

Each input has a `Validator` accessible via `formValidator()`. The validator runs required checks first, then any self-validation built into the input type, then any custom rules you attach.

```
// Required
$input->formValidator()->setRequired(true);

// Custom rule — return an error string or null
$input->formValidator()->addRule(function($value) {
    if (strlen($value) < 8)
        return 'Must be at least 8 characters';
    return null;
});

// Check validity
$form->finalize();
if ($form->isFormAttempted()) {
    if (!$form->isValid()) {
        // errors are surfaced inline when the form renders
    }
}
```

Validation only runs after `finalize()` is called and the form has been attempted.

Disabling inputs
----------------

[](#disabling-inputs)

Inputs implementing `DisableableInput` return null from `formValue()` when disabled, regardless of what was submitted. This prevents disabled fields from being tampered with client-side.

```
$input->setFormDisabled(true);
$input->formValue(); // always null when disabled
```

Setting defaults
----------------

[](#setting-defaults)

```
$input->setFormDefault('some value');
```

The default is used as the initial value before submission, and as the step base for `NumberInput` and `DateTimeInput`.

Field wrapper
-------------

[](#field-wrapper)

The `Field` class wraps an input with its label and error display. Inputs implementing `SelfLabeledInput` (checkbox, radio, multi-checkbox) don't need a `Field` wrapper as they render their own label structure.

```
$form->addChild(new Field($text_input));   // most inputs
$form->addChild($checkbox_input);           // SelfLabeledInput
$form->addChild($radio_input);             // SelfLabeledInput
$form->addChild($multi_checkbox_input);    // SelfLabeledInput
```

License
-------

[](#license)

MIT License

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance86

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

73d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/f4e606aa41ebfd7d82c365c6dc4ec158fb1b0bfff5f8dcbfad4ab53e3092e6a6?d=identicon)[jobyone](/maintainers/jobyone)

---

Top Contributors

[![joby-lol](https://avatars.githubusercontent.com/u/856610?v=4)](https://github.com/joby-lol "joby-lol (2 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/joby-smol-form/health.svg)

```
[![Health](https://phpackages.com/badges/joby-smol-form/health.svg)](https://phpackages.com/packages/joby-smol-form)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
