This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
05.

Built-in Symfony Form Themes

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Okay... it’s finally time. The long-awaited moment where we... add styling to our form. Yes! From here on out, it’s just 17 chapters of us carefully adjusting padding, margins, and hand-crafting artisanal CSS. You can go grab a coffee... I’ll still be here massaging border-radius values.

Ha! I’m kidding! I promise I won’t do that to you. Thanks to Symfony, we have built-in themes for various CSS frameworks, which means we can bypass most of the CSS work and jump right into the fun part.

Symfony’s Built-In Form Themes

Give "Symfony form themes" a quick Google search. There you have it! Symfony provides us with a plethora of themes: classic table layouts, bootstrap themes (both default and horizontal) for various versions, foundation themes - it's like a complete fashion catalog. And the icing on the cake? If you are using Tailwind CSS, you're in luck! Symfony has a Tailwind CSS form theme too.

Applying Themes in Symfony

So, how do we use a specific form theme? Twig to the rescue! There's a special Twig tag for form themes. First, open PhpStorm and locate the new.html.twig template with our form. At the top, just under the extends tag, add a new tag with: {% form_theme form 'tailwind_2_layout.html.twig' %}:

// ... lines 1 - 2
{% form_theme form 'tailwind_2_layout.html.twig' %}
// ... lines 4 - 18

Remember to pass the specific form variable - form in this instance - so the theme knows which form to apply.

Hit refresh in your browser... and voila! Our form looks much more refined with neat spacing and alignment.

An EntityType Form Type

There's something that's still bothering me... Open src/Form/StarshipPartType.php... The starship field is an EntityType:

36 lines | src/Form/StarshipPartType.php
// ... lines 1 - 6
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// ... lines 8 - 12
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 18 - 20
->add('starship', EntityType::class, [
'class' => Starship::class,
'choice_label' => 'id',
])
// ... line 25
;
}
// ... lines 28 - 34
}

If you recall from the previous chapter, this field only requires the class option. This is the relationship that connects our StarshipPart to a Starship. It displays all the Starship's in a select element.

But look at this choice_label - set to id. This sets the property of Starship that will be displayed in the dropdown's choices. MakerBundle sets this to id by default.

How the heck will our users know which starship to pick based on an ID?

Inside the Starship entity, we have a name property:

86 lines | src/Entity/StarshipPart.php
// ... lines 1 - 10
class StarshipPart
{
// ... lines 13 - 20
private ?string $name = null;
// ... lines 22 - 84
}

Let's use that! Replace id with name:

36 lines | src/Form/StarshipPartType.php
// ... lines 1 - 12
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 18 - 20
->add('starship', EntityType::class, [
// ... line 22
'choice_label' => 'name',
])
// ... line 25
;
}
// ... lines 28 - 34
}

Hit refresh in your browser and... Boom! Much better! However, the list is quite long and seems pretty random. What if we could order these Starships by name? Sounds good, right? Let's do it!

Using a Custom Database Query for EntityType

Inside the field configuration, add a new option called query_builder. Set it to an anonymous function that accepts EntityRepository $repo. Inside this function, return $repo->createQueryBuilder('starship'). Then, ->orderBy(), first argument: starship.name, second: ASC for ascending order.

Totally not necessary, but let's get a little fancy and use an Order enum for the second argument. So instead of ASC, write Order, importing the enum from Doctrine\Common\Collections, then ::Ascending->value:

42 lines | src/Form/StarshipPartType.php
// ... lines 1 - 7
use Doctrine\ORM\EntityRepository;
// ... lines 9 - 14
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 20 - 22
->add('starship', EntityType::class, [
// ... lines 24 - 25
'query_builder' => function (EntityRepository $repo) {
return $repo->createQueryBuilder('starship')
->orderBy('starship.name', Order::Ascending->value);
},
])
// ... line 31
;
}
// ... lines 34 - 40
}

Super nerdy... But at least we know we didn't make a typo in those, ummm... 3 letters...

Since we have access to the full query builder, we can easily add custom filters, JOINs, or even complex WHERE clauses to filter the results to a specific solar system or galaxy.

Let's see if this worked!

Back in the browser, refresh the page... Cool! The starships are now sorted alphabetically by name. There's just one tiny issue - there are duplicated names. It's possible our Starships come from different galaxies, so they might have the same name. To make things clearer, let's enhance the label.

Using a Custom Callback for the Choice Label

We could display the entity ID next to the name, but I have a better idea. Let's display the captain's name next to the ship name.

We could create a method like getNameWithCaptain() in the Starship entity, which would return the name of the Starship and the name of its captain. That would be a good solution... especially if you need this value elsewhere in your app.

However, let's keep it simple for now. For the starship's choice_label option, instead of name, set it to an anonymous function that accepts Starship $starship. Inside, return sprintf('%s (by %s)'), passing $starship->getName() and $starship->getCaptain() as the placeholder values:

48 lines | src/Form/StarshipPartType.php
// ... lines 1 - 14
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 20 - 22
->add('starship', EntityType::class, [
// ... line 24
'choice_label' => function (Starship $starship) {
return sprintf(
'%s (by %s)',
$starship->getName(),
$starship->getCaptain(),
);
},
// ... lines 32 - 35
])
// ... line 37
;
}
// ... lines 40 - 46
}

Hit refresh... and now we're cooking! It's so much easier to identify each ship.

Setting Form Field Attributes in the Form Type

Let's address the ugly button in the room! The "Create and add new" submit button looks gross.

There are a couple ways we can add CSS classes to style it nicely. The easiest is to use the attr option in the form field configuration.

Back in our form type, add an option array to the createAndAddNew field with 'attr' => []. This is an array of HTML attributes we want to add to the button. Inside, add class => '', jump back to our new.html.twig template, copy the classes we used for the first button, and paste them here. Replace bg-green-700 with bg-blue-700 to give it a visual distinction:

52 lines | src/Form/StarshipPartType.php
// ... lines 1 - 14
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 20 - 36
->add('createAndAddNew', SubmitType::class, [
'attr' => [
'class' => 'text-white bg-blue-700 hover:bg-blue-800 rounded-lg px-5 py-2.5 me-2 mb-2 cursor-pointer',
],
])
;
}
// ... lines 44 - 50
}

Back in the browser, hit refresh and there we have it! Our button looks fantastic and is ready for action. Any HTML attribute can be added in this manner. For instance, you can set id, placeholder, or even a data attribute to make fields interactive with Stimulus! There's also a sibling option called label_attr if you need to style the <label> tag of the field

We did quite a bit of fine-tuning and our form now looks sharp and behaves beautifully. Up next is something truly exciting and super important: validation. Stay tuned!