PHPackages                             efabrica/neoforms - 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. efabrica/neoforms

ActiveLibrary

efabrica/neoforms
=================

Better way to render forms

3.5.1(4mo ago)549.7k↓22.6%8MITPHPPHP ^8.3

Since Jun 30Pushed 4mo ago4 watchersCompare

[ Source](https://github.com/efabrica-team/neoforms)[ Packagist](https://packagist.org/packages/efabrica/neoforms)[ RSS](/packages/efabrica-neoforms/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (8)Versions (51)Used By (0)

[![NeoForms](./src/.img/logo.png)](./src/.img/logo.png)

[![Packagist Version](https://camo.githubusercontent.com/9bc2365cb5c20654776a37f9923da06648eb27c54776d380572be15a09c314c5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f65666162726963612f6e656f666f726d73)](https://camo.githubusercontent.com/9bc2365cb5c20654776a37f9923da06648eb27c54776d380572be15a09c314c5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f65666162726963612f6e656f666f726d73)[![Packagist Downloads](https://camo.githubusercontent.com/36ec16ca9ef1ce52759c9472b2f1fbde71e07c6cc0a9d3f3b85a4ba77eab0e08/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f65666162726963612f6e656f666f726d73)](https://camo.githubusercontent.com/36ec16ca9ef1ce52759c9472b2f1fbde71e07c6cc0a9d3f3b85a4ba77eab0e08/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f65666162726963612f6e656f666f726d73)

NeoForms are the very much needed medicine for Nette\\Forms.

- Gives you an easier way to write your own renderer templates
- Gives you conventions to collaborate with your team more efficiently
- Gives you a way to render form fields individually with the `{formRow}`, `{formLabel}` and `{formInput}` tags that you could be used to from Symfony.
- Gives you a way to render rows and columns when building the form in PHP
- Gives you a `readonly` mode to render a form for people who can't edit it by rendering regular text instead of inputs. (Say goodbye to grayed-out disabled fields!)
- Gives you FormCollection for pre-styled AJAX-less Multiplier with built-in diff calculator
- ControlGroup inside of a ControlGroup (tree structure)

Installation
============

[](#installation)

```
composer require efabrica/neoforms
```

```
# config.neon
includes:
    - ../../vendor/efabrica/neoforms/config.neon
```

Documentation
=============

[](#documentation)

- [Using ActiveRowForm](#using-activerowform)
    - [Presenter](#presenter)
    - [Using component in latte (simple rendering)](#using-component-in-latte-simple-rendering)
    - [Using component in latte (custom HTML structure around it)](#using-component-in-latte-custom-html-structure-around-it)
    - [Using component in latte (stand-alone HTML template for form)](#using-component-in-latte-stand-alone-html-template-for-form)
- [{formGroup} example](#formgroup-example)
- [.row .col grid layout in PHP](#row-col-grid-layout-in-php)
- [Latte Tags (API) Documentation](#latte-tags-api-documentation)
    - [`{neoForm}`](#neoform)
    - [`{formRow}`](#formrow)
    - [`{formGroup}`](#formgroup)
    - [`{formLabel}`](#formlabel)
    - [`{formInput}`](#forminput)
- [Applying Attributes](#applying-attributes)
    - [`"icon"`](#icon)
    - [`"description"`](#description)
    - [`"info"`](#info)
    - [`"readonly"`](#readonly)
    - [`"class"`](#class)
    - [`"input"` and `"label"`](#input-and-label)
- [Custom Template](#custom-template)

### Using ActiveRowForm

[](#using-activerowform)

```
use Efabrica\NeoForms\Build\NeoForm;
use Efabrica\NeoForms\Build\NeoFormFactory;
use Efabrica\NeoForms\Build\NeoFormControl;
use Efabrica\NeoForms\Build\ActiveRowForm;
use Nette\Database\Table\ActiveRow;
use Nette\Application\UI\Template;

class CategoryForm extends ActiveRowForm
{
    private NeoFormFactory $formFactory;
    private CategoryRepository $repository;

    // There is no parent constructor
    public function __construct(NeoFormFactory $formFactory, CategoryRepository $repository) {
        $this->formFactory = $formFactory;
        $this->repository = $repository;
    }

    /**
     * NeoFormControl is attached to presenter and used in template.
     *
     * @param ActiveRow|null $row Optional ActiveRow parameter to fill the form with data
     * @return NeoFormControl
     */
    public function create(?ActiveRow $row = null): NeoFormControl
    {
        $form = $this->formFactory->create();

        $form->addText('name', 'Category Name')
            ->setHtmlAttribute('placeholder', 'Enter category name')
            ->setRequired('Name is required to fill');

        $form->addText('description', 'Category Description')
            ->setHtmlAttribute('placeholder', 'Type category description')
            ->setRequired('Description is required to fill');

        $form->addSubmit('save', ($row === null ? 'Edit' : 'Create') . ' Category')
            ->setIcon('save');

        return $this->control($form, $row);
    }
}
```

### Component Usage in Presenter

[](#component-usage-in-presenter)

```
class CategoryPresenter extends AdminPresenter
{
    private CategoryForm $form;
    private CategoryRepository $repository;

    public function actionCreate(): void
    {
        $this->addComponent($this->form->create(), 'categoryForm');
    }

    public function actionUpdate(int $id): void
    {
        $row = $this->repository->findOneById($id);
        if (!$row instanceof \Nette\Database\Table\ActiveRow) {
            throw BadRequestException();
        }
        $this->addComponent($this->form->create($row), 'categoryForm');
    }
}
```

### Component Usage in Latte Templates

[](#component-usage-in-latte-templates)

#### Simple Rendering

[](#simple-rendering)

```
{* create.latte *}
{block content}

        {control categoryForm}

```

#### Custom HTML Structure inside the `` tag

[](#custom-html-structure-inside-the-form-tag)

```
{* create.latte *}
{block content}

        {neoForm categoryForm}
            {formRow $form['name'], data-joke => 123} {* adds [data-joke="123"] to the wrapping div *}
            {formRow $form['description']}

              {formRow $form['is_pinned']}
              {formRow $form['is_highlight']}
              {formRow $form['is_published']}

            {formRow $form['save'], input => [class => 'reverse']} {* sets input's class to 'reverse' *}
        {/neoForm}

```

#### Stand-alone HTML Template for Form

[](#stand-alone-html-template-for-form)

```
{* categoryForm.latte *}
{neoForm categoryForm}
    {formRow $form['name'], data-joke => 123} {* adds [data-joke="123"] to the wrapping div *}
    {formRow $form['description']}
    {formRow $form['save'], input => [class => 'reverse']} {* sets input's class to 'reverse' *}
{/neoForm}
```

### Grouping Form Elements

[](#grouping-form-elements)

```
/** @var \Efabrica\NeoForms\Build\NeoForm $form */
$names = $form->group('names');
$names->addText('id', 'ID');
$names->addText('icon', 'Icon');

$checkboxes = $form->group('checkboxes');
$checkboxes->addToggleSwitch('enabled', 'Enabled');
$checkboxes->addCheckbox('verified', 'Verified');
```

```
{neoForm categoryForm}

        {formGroup $form->getGroup('names')} {* renders id & icon *}

        {formGroup $form->getGroup('checkboxes')} {* renders enabled & verified *}

{/neoForm}
```

### .row .col Grid Layout in PHP

[](#row-col-grid-layout-in-php)

```
/** @var \Efabrica\NeoForms\Build\NeoForm $form */
$row1 = $form->row(); // returns a row instance
$col1 = $row1->col('6'); // returns a new col instance, class="col-6"
$col1->addText('a');
$col1->addTextArea('b');
$col2 = $row1->col('6'); // returns a new different col instance
$col2->addCheckbox('c');

$a = $form->row('main');
$b = $form->row('main');
assert($a === $b); // true, it's the same instance
```

---

Latte Tags (API) Documentation
------------------------------

[](#latte-tags-api-documentation)

### `{neoForm}`

[](#neoform)

The `{neoForm}` tag is used to render the `` element in your HTML. It can also render all the unrendered inputs at the end of the form. The argument for this tag is the name of the control without quotes.

> To render an entire form without specifying any sub-elements, use the following syntax:
>
> ```
> {neoForm topicForm}{/neoForm}
>
> ```

> If you want to exclude certain form fields from rendering, you can use `rest => false` like this:
>
> ```
> {neoForm topicForm, rest => false}
> {/neoForm}
>
> ```
>
>
>
> This will render an empty ``, similar to using an empty `{form}` tag.

---

### `{formRow}`

[](#formrow)

The `{formRow}` tag is used to render a form label and form input inside a wrapping group. It accepts various options. The argument for this tag is a `BaseControl` instance (e.g., `$form['title']`).

Here are some examples of how to use `{formRow}`:

> ```
> {formRow $form['title'], class => 'mt-3'}
> ```
>
>
>
> This renders a form row with a custom class, resulting in `...`.

> ```
> {formRow $form['title'], '+class' => 'mt-3'}
> ```
>
>
>
> If you are using a Bootstrap template, this will render a form group with a class, resulting in `...`.

You can also add attributes to the input or label elements using options:

> ```
> {formRow $form['title'], input => [data-tooltip => 'HA!']}
> ```
>
>
>
> This renders a form row with an input element that has a `data-tooltip` attribute.

> ```
> {formRow $form['title'], label => [data-toggle => 'modal']}
> ```
>
>
>
> This renders a form row with a label element that has a `data-toggle` attribute.

---

### `{formGroup}`

[](#formgroup)

The `{formGroup}` tag accepts a `ControlGroup` as a required argument and renders all controls in the group. It internally uses `{formRow}`to handle rendering.

Example usage:

```
{formGroup $form->getGroup('main')}
```

---

### `{formLabel}`

[](#formlabel)

The `{formLabel}` tag is used to render a `` element. The argument is a `BaseControl` instance.

Example usage:

> ```
> {formLabel $form['title'], class => 'text-large', data-yes="no"}
> ```
>
>
>
> This renders a label element with a custom class and data attributes.

If the form element is a hidden field or checkbox, the label is rendered as an empty HTML string.

---

### `{formInput}`

[](#forminput)

The `{formInput}` tag is used to render an ``, ``, ``, or any other essential part of a form row. The argument is a `BaseControl` instance.

Example usage:

> ```
> {formInput $form['category'], data-select2 => true}
> ```
>
>
>
> This renders an input element with an empty `data-select2` attribute.

---

Applying Attributes
-------------------

[](#applying-attributes)

Attributes can be applied to form elements using options. Here are some commonly used attributes:

### `"icon"`

[](#icon)

The `"icon"` attribute, when applied to buttons, adds an icon before the text. For example:

```
$form->addSubmit('save', 'Save')->setOption('icon', 'fa fa-save');
```

You can customize how the icon is added in your template.

### `"description"`

[](#description)

The `"description"` attribute adds gray helper text under input elements. For example:

```
$form->addPassword('password', 'Password')->setOption('description', 'At least 8 characters.');
```

### `"info"`

[](#info)

The `"info"` attribute adds a blue info circle tooltip next to the label. For example:

```
$form->addText('title', 'Title')->setOption('info', 'This appears on homepage');
```

### `"readonly"`

[](#readonly)

The `"readonly"` attribute, when set to true, makes the value non-modifiable and not submitted. It is rendered as a badge. Examples:

```
$form->addText('title', 'Title')->setOption('readonly', true);
// or
{formRow $form['title'], readonly => true}
// or
$form->setReadonly(true); // to make the entire form readonly
// or
{neoForm yourForm, readonly => true} // to make the entire form readonly
```

You can also provide a callback function for dynamic readonly behavior.

### `"class"`

[](#class)

The `"class"` attribute allows you to override a class or any other HTML attribute to the row/input/label. For example:

```
$form->addText('title', 'Title')->setOption('class', 'form-control form-control-lg');
```

> If you want to keep the classes from your template, use `+class` instead:
>
> ```
> $form->addText('title', 'Title')->setOption('+class', 'form-control-lg');
> ```
>
>
>
> This also works:
>
> ```
> {formRow $form['title'], input => ['+class' => 'form-control-lg']}`
> ```

> If you want to force remove a class from your template, use false instead:
>
> ```
> $form->addText('title', 'Title')->setOption('class', false);
> ```

### `"input"` and `"label"`

[](#input-and-label)

You can apply these attributes to the `{formRow}` tag to pass HTML attributes to the input and label elements, respectively. Example:

```
{formRow $form['title'], 'input' => ['class' => 'special']}
{formRow $form['title'], 'label' => ['class' => 'special']}
```

---

FormCollection
--------------

[](#formcollection)

### Usage:

[](#usage)

```
use Efabrica\NeoForms\Build\NeoContainer;
// Create a new collection called "sources"
$form->addCollection('sources', 'Sources', function (NeoContainer $container) {
    // Add some fields to the collection
    $container->addText('bookTitle', 'Book Title');
    $container->addInteger('Year', 'Year');
    // Add another collection for authors
    $container->addCollection('authors', 'Authors', function (NeoContainer $container) {
        $container->addText('author', 'Author');
    });
});
```

You render the form as any other control in the form. (`{formRow}` or automatically)

Processing

```
protected function onUpdate(NeoForm $form, array $values, ActiveRow $row): void
{
    // To process the form, you can get the new state of the collection like this:
    $sources = $values['sources'];

    // If you want to use the Diff API, you can do something like this:
    $diff = $form['sources']->getDiff();
    foreach($diff->getAdded() as $newRow) {
        $this->sourceRepository->insert($newRow);
    }
    foreach($diff->getDeleted() as $removedRow) {
        $this->sourceRepository->delete($removedRow);
    }
    foreach($diff->getModified() as $updatedRow) {
        $row = $this->sourceRepository->findOneBy($updatedRow->getOldRow());
        $row->update($updatedRow->getDiff());
    }
}
```

You don't have to use the Diff API. If you for example only use a simple collection of single text inputs, you might find it easier to just persist the new array of values.

---

Custom Template
---------------

[](#custom-template)

To create your own extended template for rendering forms, you can follow our examples. Below is a step-by-step guide on how to create your custom extended template using Bootstrap 4 as an example:

**Step 1: Create a New PHP Class**

Create a new PHP class for your extended template by extending the base template class. In this example, we'll call it `Bootstrap5FormTemplate`. Make sure to place this class in an appropriate namespace, just like in the provided code.

```
namespace Your\Namespace\Here;

use Efabrica\NeoForms\Render\Template\NeoFormTemplate;
// ... Import other necessary classes here ...

class Bootstrap4FormTemplate extends NeoFormTemplate
{
    // Your template implementation goes here
}
```

**Step 2: Customize Form Elements**

Override the methods in your extended template class to customize the rendering of form elements according to your preferred Bootstrap 4 styling. For example, you can define how text inputs, buttons, checkboxes, and other form elements should be rendered with Bootstrap classes.

Here's an example of customizing the rendering of text inputs:

```
protected function textInput(TextInput $control, array $attrs): Html
{
    $el = $control->getControl();
    $el->class ??= 'form-control'; // Add Bootstrap class, if no class was specified through ->setHtmlAttribute()
    return $this->applyAttrs($el, $attrs);
}
```

**Step 3: Customize Form Labels**

You can also customize how form labels are rendered. In Bootstrap, you may want to add the `col-form-label` class for proper alignment. Override the `formLabel` method to achieve this:

```
public function formLabel(BaseControl $control, array $attrs): Html
{
    $el = $control->getLabel();
    $el->class ??= 'col-form-label'; // Add Bootstrap class
    $el->class('required', $control->isRequired())->class('text-danger', $errors !== []);
    $this->addInfo($control, $el);

    foreach ($errors as $error) {
        $el->addHtml(
            Html::el('span')->class('c-button -icon -error -tooltip js-form-tooltip-error')
                ->setAttribute('data-bs-toggle', 'tooltip')
                ->title($control->translate($error))
                ->addHtml(Html::el('i', 'warning')->class('material-icons-round'))
        );
    }
    // Customize label rendering as needed
    return $this->applyAttrs($el, $attrs);
}
```

**Step 4: Customize Buttons**

For buttons, you can add Bootstrap classes and icons if desired. Customize the rendering of buttons like this:

```
protected function button(Button $control, array $attrs): Html
{
    $el = $control->getControl();
    $el->class ??= 'btn btn-primary'; // Add Bootstrap 4 classes
    $icon = $control->getOption('icon');
    if (is_string($icon) && trim($icon) !== '') {
        $el->insert(0, Html::el('i')->class("fa fa-$icon")); // Add an icon if available
    }
    // Customize button rendering as needed
    return $this->applyAttrs($el, $attrs);
}
```

**Step 5: Customize Other Form Elements**

Repeat similar customization for other form elements like checkboxes, radio buttons, select boxes, etc., based on your desired styling.

**Step 6: Implement Additional Styling**

If your template requires additional styling for specific elements or form groups, you can do so in your extended template class.

**Step 7: Apply Your Custom Template**To use your custom template, you need to instantiate it and set it as the template for your forms when rendering. For example:

```
use Your\Namespace\Here\BootstrapFormTemplate;
use Efabrica\NeoForms\Build\NeoForm;

// Instantiate your custom template
$template = new Bootstrap4FormTemplate();

// Create a NeoForm instance and set the custom template
$form = new NeoForm();
$form->setTemplate($template);

// Render your form
echo $form;
```

> If you want to use your custom template for all forms, you can set it as the default template by rewiring your auto-wiring :)
>
> ```
> # config.neon
> services:
>    neoForms.template: Your\Namespace\Here\Bootstrap4FormTemplate()
> ```

By following these steps, you can create your own extended template for rendering forms in a way that aligns with Bootstrap 4 or any other custom styling you prefer. Customize the template methods according to your specific styling needs.

###  Health Score

56

—

FairBetter than 98% of packages

Maintenance77

Regular maintenance activity

Popularity36

Limited adoption so far

Community20

Small or concentrated contributor base

Maturity76

Established project with proven stability

 Bus Factor1

Top contributor holds 80.5% 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

Every ~34 days

Recently: every ~27 days

Total

39

Last Release

126d ago

Major Versions

0.0.2 → 1.0.02022-08-18

1.7.1 → 2.0.02023-03-13

2.5.0 → 3.0.02023-10-03

PHP version history (2 changes)0.0.1PHP ^7.4 || ^8.1

3.2.1PHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/25289c62a88df1c404543693ba52e44748dbce486bcfab91fc8c5931a6bb38e1?d=identicon)[dev-efabrica](/maintainers/dev-efabrica)

---

Top Contributors

[![riki137](https://avatars.githubusercontent.com/u/1223388?v=4)](https://github.com/riki137 "riki137 (70 commits)")[![marcelvrana](https://avatars.githubusercontent.com/u/47558347?v=4)](https://github.com/marcelvrana "marcelvrana (6 commits)")[![tonop01](https://avatars.githubusercontent.com/u/29866948?v=4)](https://github.com/tonop01 "tonop01 (4 commits)")[![Carmagedon](https://avatars.githubusercontent.com/u/3016080?v=4)](https://github.com/Carmagedon "Carmagedon (2 commits)")[![lulco](https://avatars.githubusercontent.com/u/9377319?v=4)](https://github.com/lulco "lulco (1 commits)")[![janystucka](https://avatars.githubusercontent.com/u/206697690?v=4)](https://github.com/janystucka "janystucka (1 commits)")[![Martin-Beranek](https://avatars.githubusercontent.com/u/89643709?v=4)](https://github.com/Martin-Beranek "Martin-Beranek (1 commits)")[![raky2702](https://avatars.githubusercontent.com/u/127527898?v=4)](https://github.com/raky2702 "raky2702 (1 commits)")[![andrejjursa](https://avatars.githubusercontent.com/u/2331773?v=4)](https://github.com/andrejjursa "andrejjursa (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/efabrica-neoforms/health.svg)

```
[![Health](https://phpackages.com/badges/efabrica-neoforms/health.svg)](https://phpackages.com/packages/efabrica-neoforms)
```

###  Alternatives

[nette/nette

👪 Nette Framework - innovative framework for fast and easy development of secured web applications in PHP (metapackage)

1.6k2.8M335](/packages/nette-nette)[nette/code-checker

✅ Nette CodeChecker: A simple tool to check source code against a set of Nette coding standards.

881.7M6](/packages/nette-code-checker)[o5/grido

Grido - DataGrid for Nette Framework

87290.5k4](/packages/o5-grido)[nextras/datagrid

Datagrid component for Nette Framework.

71268.5k4](/packages/nextras-datagrid)[nette/web-project

Nette: Standard Web Project

10991.8k](/packages/nette-web-project)[contributte/forms-bootstrap

Nette extension for Bootstrap forms

211.1M4](/packages/contributte-forms-bootstrap)

PHPackages © 2026

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