Form Rendering

Forms' appearances can differ greatly. In fact, there are two extremes. One side is the need to render a set of very similar forms all over again, with little to none effort. Usually administrations and back-ends.

The other side is tiny sweet forms, each one being a piece of art. Their layout can best be written in HTML. Of course, besides those extremes, there are many forms just in between.

Default Renderer

Renderer automatically renders a form. It's set with setRenderer() method on a form and it gains control when calling $form->render() or echo $form. If we set no custom renderer, Nette\Forms\Rendering\DefaultFormRenderer is used. All you have to write is:

echo $form;

or in Latte:

{control form}

and the form is alive. All input fields are rendered into an HTML table. The output could look like this:

<tr class="required">
	<th><label class="required" for="frm-name">Name:</label></th>

	<td><input type="text" class="text" name="name" id="frm-name" value="" /></td>

<tr class="required">
	<th><label class="required" for="frm-age">Age:</label></th>

	<td><input type="text" class="text" name="age" id="frm-age" value="" /></td>


Nicely formatted, isn't it? :-)

It's up to you, whether to use a table or not, and many web designers prefer different markups, for example a list. We may configure DefaultFormRenderer so it would not render into a table at all. We just have to set proper $wrappers. The first index always represents an area and the second one it's element. All respective areas are shown in the picture:

By default a group of controls is wrapped in <table>, and every pair is a table row <tr> containing a pair of label and control (cells <th> and <td>). Let's change all those wrapper elements. We will wrap controls into <dl>, leave pair by itself, put label into <dt> and wrap control into <dd>:

$renderer = $form->getRenderer();
$renderer->wrappers['controls']['container'] = 'dl';
$renderer->wrappers['pair']['container'] = null;
$renderer->wrappers['label']['container'] = 'dt';
$renderer->wrappers['control']['container'] = 'dd';

echo $form;

Results into the following snippet:

	<dt><label class="required" for="frm-name">Name:</label></dt>

	<dd><input type="text" class="text" name="name" id="frm-name" value="" /></dd>

	<dt><label class="required" for="frm-age">Age:</label></dt>

	<dd><input type="text" class="text" name="age" id="frm-age" value="" /></dd>


Wrappers can affect many attributes. For example:

  • add special CSS classes to each form input
  • distinguish between odd and even lines
  • make required and optional draw differently
  • set, whether error messages are shown above the form or close to each element

Bootstrap Support

You can find examples of configuration forms for Twitter Bootstrap 2, Bootstrap 3 and Bootstrap 4

Manual Rendering

You can render forms manually for better control over the generated code. Place the form inside {form myForm} and {/form} pair tags. Inputs can be rendered using {input myInput} tag, which renders the input, and {label myInput /}, which renders its the label.

{form signForm}

<!-- Simple errors rendering -->
<ul class="errors" n:if="$form->hasErrors()">
	<li n:foreach="$form->errors as $error">{$error}</li>

<tr class="required">
	<th>{label name /}</th>
	<td>{input name}</td>

<!--If you need manualy render radiolist -->
<p>{input radioList:itemKey} | {input radioList:itemKeyTwo}</p>



You can also connect a form with a template easily by using n:name attribute.

protected function createComponentSignInForm(): Form
	$form = new Form;
	return $form;
<form n:name=signInForm class=form>
	<p><label n:name=user>Username: <input n:name=user size=20></label>
	<p><label n:name=password>Password: <input n:name=password></label>
	<p><input n:name=send class="btn btn-default">

You can also use n:name with <select>, <button> or <textarea> elements and the content.

You can render RadioList, Checkbox or CheckboxList by HTML elements individually. This is called partial rendering:

{foreach $form[gender]->items as $key => $label}
	<label n:name="gender:$key"><input n:name="gender:$key"> {$label}</label>

Or you can use basic tags {input gender:$key} and {label gender:$key}. The trick is using the colon. For a simple checkbox, use {input myCheckbox:}.

Tag formContainer helps with rendering of inputs inside a form container.

{form signForm}
	<th>Which news you wish to receive:</th>
		{formContainer emailNews}
			<li>{input sport} {label sport /}</li>
			<li>{input science} {label science /}</li>

How to set more attributes to HTML elements? Methods getControl() and getLabel() return element as a Nette\Utils\Html objects, which can be easily adjusted. In Latte:

{form signForm class => 'big'}
<tr class="required">
	<th>{label name /}</th>
	<td>{input name cols => 40, autofocus => true}</td>

Grouping Inputs

Input fields can be grouped into visual field-sets by creating a group:

$form->addGroup('Personal data');

Creating new group activates it – all elements added further are added to this group. You may build a form like this:

$form = new Form;
$form->addGroup('Personal data');
$form->addText('name', 'Your name:');
$form->addInteger('age', 'Your age:');
$form->addEmail('email', 'Email:');

$form->addGroup('Shipping address');
$form->addCheckbox('send', 'Ship to address');
$form->addText('street', 'Street:');
$form->addText('city', 'City:');
$form->addSelect('country', 'Country:', $countries);

HTML Attributes

You can set any HTML attributes to form fields:

$form->addInteger('number', 'Number:')
	->setHtmlAttribute('class', 'bigNumbers');

$form->addSelect('rank', 'Order by:', ['price', 'name'])
	->setHtmlAttribute('onchange', 'submit()'); // calls JS function submit() on change

// applying on <form> - since nette/forms 3.0.3
$form->setHtmlAttribute('id', 'myForm');

// before 3.0.3
$form->getElementPrototype()->id = 'myForm';

Setting input type:

$form->addText('tel', 'Your telephone:')
	->setHtmlAttribute('placeholder', 'Please, fill in your telephone');

We can set HTML attribute to individual items in radio or checkbox lists with different values for each of them. Note the colon after style: to ensure that the value is selected by key:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$styles = ['r' => 'background:red', 'g' => 'background:green'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('style:', $styles);


<label><input type="checkbox" name="colors[]" style="background:red" value="r">red</label>
<label><input type="checkbox" name="colors[]" style="background:green" value="g">green</label>
<label><input type="checkbox" name="colors[]" value="b">blue</label>

For a logical HTML attribute (which has no value, such as readonly), you can use a question mark:

$colors = ['r' => 'red', 'g' => 'green', 'b' => 'blue'];
$form->addCheckboxList('colors', 'Colors:', $colors)
	->setHtmlAttribute('readonly?', 'r'); // use array for multiple keys, e.g. ['r', 'g']


<label><input type="checkbox" name="colors[]" readonly value="r">red</label>
<label><input type="checkbox" name="colors[]" value="g">green</label>
<label><input type="checkbox" name="colors[]" value="b">blue</label>

For selectboxes, the setHtmlAttribute() method sets the attributes of the <select> element. If we want to set the attributes for each <option>, we will use the method setOptionAttribute() (since version 3.0.4). Also, the colon and question mark used above work:

$form->addSelect('colors', 'Colors:', $colors)
	->setOptionAttribute('style:', $styles);


<select name="colors">
	<option value="r" style="background:red">red</option>
	<option value="g" style="background:green">green</option>
	<option value="b">blue</option>

Other setting

Setting description (rendered after the input by default):

$form->addText('phone', 'Number:')
	->setOption('description', 'This number will remain hidden');

If we want to place HTML content into it, we use Html class.

use Nette\Utils\Html;

$form->addText('phone', 'Phone:')
	->setOption('description', Html::el('p')
		->setHtml('This number remains hidden. <a href="...">Terms of service.</a>')

Html element can be also used instead of label: $form->addCheckbox('conditions', $label).

Related blog posts