Best Practices for Theming Symfony Forms

Many Symfony developers struggle at customizing the HTML output of a Symfony form. Rendering a prototype is easy:

{{ form(form) }}}

But no real application is as simple as that. Real applications usually need a more complex and customized HTML. So you start using all of the advanced tooling that Symfony provides, from custom blocks over form themes, theme inheritance and many more. Real soon your template becomes quite complicated. Wouldn't it be much easier to write everything by hand? Yes it would!

Let me guide you through a few what I consider best practices for rendering your forms with Symfony.

The 90% Use Case

Most forms in most applications are quite simple, like a typical contact or registration form. Should you use Symfony for these forms?

I think you should. Symfony provides a lot of functionality for handling the input of your form and transforming it to data structures that you can use easily in your business logic. Writing this transformation code by hand takes time, is boring and prone to error.

What you should write by hand however is your HTML. Most of the time, there is no need to introduce the complexity of form themes into your Twig templates, especially not if you don't fully understand how they work.

Let's look at the Twig functions that we need:

Outputs the opening <form> tag.
Outputs the closing </form> tag.
Outputs a field tag (<input>, <select>, <textarea>).
Outputs the label of a field.
Outputs an <ul> tag with the errors of a field.

Prefer using form_start() and form_end() over writing <form> and </form>. These methods do some extra work like rendering hidden fields (CSRF token etc.) or setting the form's method to the one expected in the controller.

The HTML of our form could look something like this:

{% form_theme form "bootstrap_3_layout.html.twig" %}

{{ form_start(form) }}
        <legend>Send a Message</legend>
        <p>Use the form below to leave me a message.</p>

        {{ form_errors(form) }}

        <div class="form-group">
            {{ form_label(form.senderName) }}
            {{ form_widget(form.senderName) }}
            {{ form_errors(form.senderName) }}

        <div class="form-group">
            {{ form_label(form.senderEmail) }}
            {{ form_widget(form.senderEmail) }}
            {{ form_errors(form.senderEmail) }}

        <div class="form-group">
            {{ form_label(form.message) }}
            {{ form_widget(form.message) }}
            {{ form_errors(form.message) }}

        <div class="form-group">
            {{ form_widget(form.submit) }}
{{ form_end(form) }}