Can I put html inside a Symfony form button with Twig?

If you're looking for a simpler solution, just insert this into your form theme:

{%- block button_widget -%}
    {% set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) %}
    {%- if label is empty -%}
        {%- if label_format is not empty -%}
            {% set label = label_format|replace({
                '%name%': name,
                '%id%': id,
            }) %}
        {%- else -%}
            {% set label = name|humanize %}
        {%- endif -%}
    {%- endif -%}
    <button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ label|trans({}, translation_domain)|raw }}</button>
{%- endblock button_widget -%}

You can then go ahead and insert HTML in your button label:

{{ form_widget(searchForm.search, {'label': '<span class="glyphicon glyphicon-search" aria-hidden="true"></span>'}) }}

Yes, but you'll have to customise your form theme.

Note: This answer has been edited to be compatible with Symfony 2.8 3.x and 4.x. For older versions, please see the edit history.

A nice way to support icons in buttons is using form extensions. First create a form extension class that defines a new property icon that you can use in your forms:

<?php

namespace Foo\BarBundle\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ButtonTypeIconExtension extends AbstractTypeExtension
{
    /**
     * @param FormBuilderInterface $builder
     * @param array                $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->setAttribute('icon', $options['icon']);
    }

    /**
     * @param FormView      $view
     * @param FormInterface $form
     * @param array         $options
     */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['icon'] = $options['icon'];
    }

    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(['icon' => null]);
        $resolver->setDefined(['icon']);
    }

    /**
     * Returns the name of the type being extended.
     *
     * @return string The name of the type being extended
     */
    public function getExtendedType()
    {
        return ButtonType::class; // Extend the button field type
    }
}

Register this extension in your services.yml (or xml file). The alias must correspond with the string returned by the above getExtendedType() method.

# Form extension for adding icons
foobar.form_extension.icon:
    class: Foo\BarBundle\Form\Extension\ButtonTypeIconExtension
    tags:
        - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\ButtonType }

Next, override your form_div_layout.html.twig. (See link above) You can now use icon as a variable in these themes. For buttons we override the button_widget block:

{% block button_widget -%}
    {% set attr = attr|merge({class: (attr.class|default('') ~ ' btn')|trim}) %}
    {% if label is empty -%}
        {%- if label_format is not empty -%}
            {% set label = label_format|replace({
                '%name%': name,
                '%id%': id,
            }) %}
        {%- else -%}
            {% set label = name|humanize %}
        {%- endif -%}
    {%- endif -%}
    {% if icon|default %}
        {% set iconHtml = '<i class="fa ' ~ icon ~ '"></i> ' %}
    {% else %}
        {% set iconHtml = '' %}
    {% endif %}
    <button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ iconHtml|raw }}{{ label|trans({}, translation_domain) }}</button>
{%- endblock button_widget %}

Finally, you can use the icon option in your template:

{{ form_widget(form.jiraStatus, {
    'icon': 'fa-bug',
    'label': 'Bug',
    'attr':{'class': 'btn btn-large btn-default btn-block' }
}) }}

Or in your form classes:

    $builder
        ->add('jiraStatus', SubmitType::class, [
                'label' => 'Bug',
                'icon' => 'fa-bug',
                'attr' => [
                    'class' => 'btn btn-large btn-default btn-block',
                ],
            ]
        );

Note: Is is generally better to add the icon in the template since icons are a matter of presentation, and your form classes should really be about buisness logic.

Make it even more generic:

By returning the FQCN of ButtonType in getExtendedType() we tell Symfony that we are extending all possible form elements that inherit from ButtonType such as SubmitType. Unfortunately there is no type we can use to target all possible form elements but we can add an extra extension that targets FormType. All form fields like input boxes and select elements inherit from this type. So if you want it to work with both form fields and buttons, I suggest the following:

Create an abstract class abstract class AbstractIconExtension extends AbstractTypeExtension with exactly the same content as above but leave out the getExtendedType method. Then create two classes that extend from this class (e.g. FieldTypeIconExtension and ButtonTypeIconExtension) which only contain the getExtendedType method. One returning the FQCN of FormType and the other returning the FQCN of ButtonType:

Foo/BarBundle/Form/Extension/ButtonTypeIconExtension.php:

<?php

namespace Foo\BarBundle\Form\Extension;

use Symfony\Component\Form\Extension\Core\Type\ButtonType;

class ButtonTypeIconExtension extends AbstractIconExtension
{
    /**
     * Returns the name of the type being extended.
     *
     * @return string The name of the type being extended
     */
    public function getExtendedType()
    {
        return ButtonType::class;  // extend all buttons
    }
}

Foo/BarBundle/Form/Extension/FieldTypeIconExtension.php:

<?php

namespace Foo\BarBundle\Form\Extension;

use Symfony\Component\Form\Extension\Core\Type\FormType;

class FieldTypeIconExtension extends AbstractIconExtension
{
    /**
     * Returns the name of the type being extended.
     *
     * @return string The name of the type being extended
     */
    public function getExtendedType()
    {
        return FormType::class;  // extend all field types
    }
}

Register these two classes in you services using the corresponding alias:

# Form extensions for adding icons to form elements
foobar.form_extension.button_icon:
    class: Foo\BarBundle\Form\Extension\ButtonTypeIconExtension
    tags:
        - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\ButtonType }
foobar.form_extension.form_icon:
    class: Foo\BarBundle\Form\Extension\FieldTypeIconExtension
    tags:
        - { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }

Now you can use the icon variable in other places in your form themes as well. For instance, to add icons to labels you can override the form_label block:

{% block form_label -%}
    {% if label is not sameas(false) -%}
        {% if not compound -%}
            {% set label_attr = label_attr|merge({'for': id}) %}
        {%- endif %}
        {% if required -%}
            {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
        {%- endif %}
        {% if label is empty -%}
            {%- if label_format is not empty -%}
                {% set label = label_format|replace({
                    '%name%': name,
                    '%id%': id,
                }) %}
            {%- else -%}
                {% set label = name|humanize %}
            {%- endif -%}
        {%- endif -%}
        {% if icon|default %}
            {% set iconHtml = '<i class="fa ' ~ icon ~ '"></i> ' %}
        {% else %}
            {% set iconHtml = '' %}
        {% endif %}
        <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ iconHtml|raw }}{{ label|trans({}, translation_domain) }}</label>
    {%- endif %}
{%- endblock form_label %} 

And then add an icon to the label of that field in your form class:

$builder
    ->add('mytextfield', TextType::class, [
            'label' => 'My fancy text field',
            'icon' => 'fa-thumbs-o-up'
        ]
    );

this is how I solved and tested with Symfony 4. In twig template:

{{form_start(form)}}
{{form_widget(form)}}
<div class="class row">
    <div class="class col-sm-offset col-sm-10">
        <button name='create' type='submit' value='create' class='btn btn-primary'>Save</button>
        <button name='cancel' type='submit' value='cancel' class='btn btn-cancel' formnovalidate='formnovalidate'>Cancel</button>
    </div>
</div>
{{form_end(form)}}

In my PHP controller form I did not add any button but just input fields.

   $form = $this->createFormBuilder($article)
        ->add('title',TextType::class, array(
            'data' => $article->getTitle(),
            'attr' => array('class' => 'form-control')))
        ->add('body', TextareaType::class, array(
            'data' => $article->getBody(),
            'required' => false,
            'attr' => array('class' => 'form-control')))
        ->getForm();

What I did to check the form submission is:

if($form->isSubmitted() ){
    if($request->request->get('create') && $form->isValid()){
        $article = $form->getData();
        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($article);
        $entityManager->flush();
    } //l'alternativa può solo essere il cancel
    return $this->redirectToRoute('article_list');    
}

Hope this can help. Good to say that even the problem of buttons alignment is solved because div is not added for every button as form add method does.

Tags:

Twig

Symfony