Symfony for frontend developers #4 – Styling Symfony forms

This article is part of a series; see Symfony for frontend developers for more.

Updated: This article was updated in December 2015 to include a few extra details based on our experience of using this guide to help with more of our projects.

Introduction

Symfony can create an entire form for you from a single line of template code.  This can be great for backend developers, but as a frontend developer, it’s a pain!  Where do the form’s HTML tags actually get created, and how can you change them!?

The basic idea

In order to style a Symfony form, you’ll often need to take a single line of Symfony form magic and break it down into its component parts which can be more easily styled.  With that in mind, let’s begin with that one-line form I mentioned at first.

(In reality, the “make me an entire form from this one line of code” is an approach that’s mainly used for prototyping.  But hang on in there – we’ll cover all the different bits of Symfony form magic by the end of the article!)

Breaking it down

Here’s the “Hello, world!” of Symfony forms: A single line from a Twig template which renders an entire form:

{{ form(form) }}

This one-liner is equivalent to the only-slightly less terse:

{{ form_start(form) }}
    {{ form_widget(form) }}
{{ form_end(form) }}

That’s not much more help to you as a frontend developer!  So, if you’re faced with something like this, you can break it down into its constituent parts.  As the Symfony documentation about form rendering explains, the above is equivalent to something like this:

{{ form_start(form) }}
   {{ form_errors(form) }}
   {{ form_row(form.task) }}
   {{ form_row(form.dueDate) }}
{{ form_end(form) }}

The example above is for a form which has “task” and “due date” fields.  You’ll need to choose your fields correctly for your project – ask the backend developer you’re working with.

(Note: The above code is as of Symfony 2.3 and above.  If you’re on an older project, refer to the Symfony documentation about form rendering for your version of Symfony.)

However, this is quite probably still too high-level for you to apply the styling you want.  You therefore need to break it down once again.  Now’s the time to get acquainted with form_div_layout.html.twig.

form_div_layout.html.twig

You’ll probably find this file in vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form

How does Symfony “know” what to do when it sees a Twig form tag like form_row?  The answer to this is found in form_div_layout.php.  This file shows you what pieces are used to make up a form_row, and then what smaller pieces make up those pieces, and so on, all the way down to proper HTML tags!  Hooray!

If you can navigate this file – and I’ll show you how -, you can replace something like {{ form_row(…) }} with its constituent parts, which means that you can style it!

Tip: You’ll see the word “form” used a lot in form_div_layout.php, such as in a statement like {{ form_errors(form) }}.  This doesn’t usually mean your entire form.  Usually it’s referring to one of the form fields – but Symfony actually treats forms and their fields in very similar ways – they’re actually equivalent types of object.  This equivalence also explains why you can do something like {{ form_widget(form) }} to render an entire form, but also {{ form_widget(form.task) }} just to render the task form-field.

To find something in form_div_layout.php, look for a Twig “block” with the right name.  For form_row, you’re looking for a line like this: {% block form_row %}

Blocks start with {% block … %} and end with {% endblock … %}.  Armed with this knowledge, you’ll see that a form_row is actually made up of the following Twig tags:

{% spaceless %}
   <div>
       {{ form_label(form) }}
       {{ form_errors(form) }}
       {{ form_widget(form) }}
   </div>
{% endspaceless %}

So if you had this line in your form:

{{ form_row(form.task) }}

… then you could replace it with the following code.  Note that it’s not an exact copy-and-paste from form_div_layout.php — see the changed bits in bold:

{% spaceless %}
   <div>
       {{ form_label(form.task) }}
       {{ form_errors(form.task) }}
       {{ form_widget(form.task) }}
   </div>
{% endspaceless %}

(The {% spaceless %} tag instructs Twig to remove any spaces between the HTML elements that follow.)

Don’t forget to replace form with a reference to the specific form element you’re handling – as I’ve done with form.task in the example above.

Can I style it now??

Yes you can.  Breaking a form down to this level will allow you to do much of the styling you need.  You can surround the label, errors and widget (i.e. input field) with divs, spans or whatever, and you can now re-order them.  You can also add classes:

{{ form_widget(form.task, { 'attr': {'class': 'form-control'} }) }}

The above applies a class to the form field.  Note that the syntax for adding classes to labels in this way is slightly different:

{{ form_label(form.task, null, { 'label_attr': { 'class': 'form-control' } }) }}

There are a couple of things to note when styling labels rather than fields: First, it’s label_attr rather than just attr.  And secondly, you pass this as the third parameter to the form_label function.  The second parameter can be used to override the label’s text by passing in something else; pass in null to use the original label unchanged (as here).

Going deeper

If the above doesn’t provide the control that you need, you can often simply repeat the process, digging deeper into the blocks provided.  However, there’ll probably come a point where you run into a block you want to include that has a variable in it, and Twig complains that it can’t find the variable.  For example:

{% block form_errors %}
{% spaceless %}
   {% if errors|length > 0 %}
   <ul>
       {% for error in errors %}
           <li>{{ error.message }}</li>
       {% endfor %}
   </ul>
   {% endif %}
{% endspaceless %}
{% endblock form_errors %}

You don’t know what that errors variable should be, so how do you make your own version of the block?  You need to follow a different approach here.  Rather than simply not using Symfony’s form methods and writing things “longhand”, you need to use Symfony’s own form methods, but formally override the blocks they use –  form_errors in this example.

If you only want to change something for the current form, read on — I’ll give you a very basic example of how to do it.  If you want to customise something for several forms in your application, or even the whole application, or do something else more advanced, you should read the article “Customising form rendering” in the Symfony cookbook.

So, to change the display of errors for one form, add the following to the top of your file:

{% form_theme form _self %}
{% block form_errors %}
   {# Put whatever you want in here. Probably copied-and-pasted from form_div_layout.html.twig #}
{% endblock form_errors %}

The first line tells Twig to look for alternative form “themes” in this file itself.  The rest is where you provide your nice updated version of the form_errors block.  Symfony will then use it whenever {{ form_errors(…) }} is called on this page.

I hope you’ve now got the tools you need to give your forms the markup they deserve!  Come back next week for an article about assets and Assetic.

Further reading

  • Customising form rendering” in the Symfony cookbook.  In particular, if you find yourself doing a lot of styling on every form you’re given, speak to your backend developer about the possibility of developing your own form theme (as explained in that article) to save you having the make the same changes every time!)
  • Symfony 2.6 and later include the ability to apply bootstrap classes to all your form automatically, using the Symfony Bootstrap form theme.  Such a useful feature – check it out!
  • If you’d like to know more about those special tags like form_row, form_widget and so on, check out the Twig Template Form Function and Variable Reference in the Symfony docs.

4 Comments

  1. Lumbendil
    Posted 13 October 2014 at 9:43 am | Permalink

    Why would you need to speak with your backend developer to create a form theme? You can create it and use it the same as you can use the _self form_theme. It’d be nice if you gave more thought on form themes.

  2. Sam
    Posted 13 October 2014 at 10:44 am | Permalink

    Hi Lumbendil!

    You’re right that you won’t necessarily have to speak to your backend developer to create a form theme. However, as I was suggesting creating an application-wide theme, I thought that it would be good for a developer to discuss this with their team before going ahead with it! Also, creating form themes can require more in-depth knowledge of forms and Symfony config than the simpler formatting explained in this post, so I thought that some FE developers might not have the confidence to do it themselves.

    What other form theming topics would you like me to cover? Or do you just mean that you’d have liked to have seen form theming given more prominence in this article?

    Thanks for reading!

  3. Posted 29 January 2015 at 3:49 pm | Permalink

    Always good to see other Symfony blogs about! Thought I’d drop you a note to say nice series, cheers Sam :)

  4. Sam
    Posted 29 January 2015 at 3:51 pm | Permalink

    Glad you like it; thanks “Code Review Chris”!

Post a Comment

Your email address is never published nor shared. Required fields are marked *

Ready to talk?

Whether you want to create a new digital product or make an existing one even better, we'd love to talk it through.

Get in touch