14.

Tailwind CSS

|

Share this awesome video!

|

What about CSS? You're free to add whatever CSS you want to app/styles/app.css. That file is already loaded on the page.

Want to use Bootstrap CSS? Check out the Asset Mapper docs on how to do that. Or, if you want to use Sass, there's a symfonycasts/sass-bundle to make that easy. Though, I recommend not jumping into Sass too quickly. A lot of the features that Sass is famous for can now be done in native CSS, like CSS variables and even CSS nesting.

Hello Tailwind

What's my personal choice for a CSS framework? Tailwind. And part of the reason is that Tailwind is insanely popular. So if you're looking for resources or pre-built components, you're going to have a lot of luck if you use Tailwind.

But Tailwind is a bit odd in one way: it's not simply a big CSS file that you plop onto your page. Instead, it has a build process that scans your code for all the Tailwind classes you're using. It then dumps a final CSS file that only contains the code you need.

In the Symfony world, if you want to use Tailwind, there's a bundle that makes it really easy. Spin over your terminal and install a new package: composer require symfonycasts - hey I know them - tailwind-bundle:

composer require symfonycasts/tailwind-bundle

For this package, the recipe doesn't do anything other than enable the new bundle. To get Tailwind rocking, one time in your project, run:

php bin/console tailwind:init

This does three things. First, it downloads a Tailwind binary in the background, which you'll never really need to think about. Second, it creates a tailwind.config.js file at the root of our project. This tells Tailwind where it needs to look in our project for Tailwind CSS classes. And third, it updated our app.css to add these three lines. These will be replaced by the real Tailwind code in the background by the binary.

Running Tailwind

Finally, Tailwind needs to be built, so we need to run a command to do that:

php bin/console tailwind:build -w

This scans our templates and output the final CSS file in the background. The -w puts it in "watch" mode: instead of building once and exiting, it watches our templates for changes. When it notices any updates, it will automatically rebuild the CSS file. We'll see that in minute.

But we should already see a difference. Let's go to the homepage. Did you see that? The base Tailwind code did a reset. For example, our h1 is now tiny!

Seeing Tailwind in Action

Let's try this out for real. Open templates/main/homepage.html.twig. Up on the h1, make this bigger by adding a class: text-2xl.

48 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<h1 class="text-2xl">
Starshop: your monopoly-busting option for Starship parts!
</h1>
// ... lines 9 - 46
{% endblock %}

As soon as we save that, you can see that tailwind noticed our change and rebuilt the CSS. And when we refresh, it got bigger!

Our source app.css file is still super simple - just those few lines we saw earlier. But view the page source and open the app.css that's being sent to our users. It's the built version from Tailwind! Behind the scenes, some magic exists that replaces those three Tailwind lines with the real Tailwind CSS code.

Automatically Running Tailwind with the symfony Binary

And... that's kind of it! It just works. Though there is an easier and more automatic way to run Tailwind. Hit Ctrl+C on the Tailwind command to stop it. Then, at the root of our project, create a file called .symfony.local.yaml. This is a config file for the symfony binary web server that we're using. Inside, add workers, tailwind, then cmd set to an array with each part of a command: symfony, console, tailwind, build, --watch, or you could use -w: it's the same.

I haven't talked about it yet, but instead of running php bin/console, we can also run symfony console followed by any command to get the same result. We'll talk about why you might want to do that in a future tutorial. But for now, consider bin/console and symfony console the same thing.

Also, by adding this workers key, it means that instead of us needing to run the command manually, when we start the symfony web server, it will run it for us in the background.

Watch. In your first tab, hit Ctrl+C to stop the web server... then re-run

symfony serve

so it sees the new config file. Watch: there it is! It's running the tailwind command in the background!

We can take advantage of this immediately. In homepage.html.twig, change this to text-4xl, spin over and... it works! We don't even need to think about the tailwind:build command anymore.

48 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<h1 class="text-4xl">
Starshop: your monopoly-busting option for Starship parts!
</h1>
// ... lines 9 - 46
{% endblock %}

And since we'll be styling with Tailwind, remove the blue background.

Copying in Styled Templates

Ok, this tutorial is not about Tailwind or how to design a website. Trust me, you do not want Ryan leading the web design charge. But I do want to have a nice-looking site... and it's also important to go through the process of working with a designer.

So let's pretend that someone else has created a design for our site. And they've even given us some HTML with Tailwind classes for that design. If you download the course code, in a tutorial/templates/ directory, we have 3 templates. One-by-one, I'm going to copy each file and paste it over the original. Don't worry, we'll look at what's happening in each of these files.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
{% block stylesheets %}
{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
<body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);">
<div class="flex flex-col justify-between min-h-screen relative">
<div>
<header class="h-[114px] shrink-0 flex flex-col sm:flex-row items-center sm:justify-between py-4 sm:py-0 px-6 border-b border-white/20 shadow-md">
<a href="#">
<img class="h-[42px]" src="{{ asset('images/starshop-logo.png') }}" alt="starshop logo">
</a>
<nav class="flex space-x-4 font-semibold">
<a class="hover:text-amber-400 pt-2" href="#">
Home
</a>
<a class="hover:text-amber-400 pt-2" href="#">
About
</a>
<a class="hover:text-amber-400 pt-2" href="#">
Contact
</a>
<a class="rounded-[60px] py-2 px-5 bg-white/10 hover:bg-white/20" href="#">
Get Started
</a>
</nav>
</header>
{% block body %}{% endblock %}
</div>
<div class="p-5 bg-white/5 mt-3 text-center">
Made with ❤️ by <a class="text-[#0086C4]" href="https://symfonycasts.com">SymfonyCasts</a>
</div>
</div>
</body>
</html>

Do homepage.html.twig...

{% extends 'base.html.twig' %}
{% block title %}Starshop: Beam up some parts!{% endblock %}
{% block body %}
<main class="flex flex-col lg:flex-row">
<aside
class="pb-8 lg:pb-0 lg:w-[411px] shrink-0 lg:block lg:min-h-screen text-white transition-all overflow-hidden px-8 border-b lg:border-b-0 lg:border-r border-white/20"
>
<div class="flex justify-between mt-11 mb-7">
<h2 class="text-[32px] font-semibold">My Ship Status</h2>
<button>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 448 512"><!--!Font Awesome Pro 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path fill="#fff" d="M384 96c0-17.7 14.3-32 32-32s32 14.3 32 32V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V96zM9.4 278.6c-12.5-12.5-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L109.3 224 288 224c17.7 0 32 14.3 32 32s-14.3 32-32 32l-178.7 0 73.4 73.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-128-128z"/></svg>
</button>
</div>
<div>
<div class="flex flex-col space-y-1.5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center" style="background: rgba(255, 184, 0, .1);">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs">in progress</p>
</div>
<h3 class="tracking-tight text-[22px] font-semibold">
<a class="hover:underline" href="{{ path('app_starship_show', {
id: myShip.id
}) }}">{{ myShip.name }}</a>
</h3>
</div>
<div class="flex mt-4">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">{{ myShip.captain }}</p>
</div>
<div class="pl-8">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">{{ myShip.class }}</p>
</div>
</div>
</div>
</aside>
<div class="px-12 pt-10 w-full">
<h1 class="text-4xl font-semibold mb-8">
Ship Repair Queue
</h1>
<div class="space-y-5">
<!-- start ship item -->
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between">
<div class="flex justify-center min-[1174px]:justify-start">
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: in progress">
<div class="ml-5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs text-nowrap">in progress</p>
</div>
<h4 class="text-[22px] pt-1 font-semibold">
<a
class="hover:text-slate-200"
href="#"
>USS LeafyCruiser</a>
</h4>
</div>
</div>
<div class="flex justify-center min-[1174px]:justify-start mt-2 min-[1174px]:mt-0 shrink-0">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">Jean-Luc Pickles</p>
</div>
<div class="pl-8 w-[100px]">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">Garden</p>
</div>
</div>
</div>
<!-- end ship item -->
</div>
<p class="text-lg mt-5 text-center md:text-left">
Looking for your next galactic ride?
<a href="#" class="underline font-semibold">Browse the {{ ships|length * 10 }} starships for sale!</a>
</p>
</div>
</main>
{% endblock %}

and finally show.html.twig.

{% extends 'base.html.twig' %}
{% block title %}{{ ship.name }}{% endblock %}
{% block body %}
<div class="my-4 px-8">
<a class="bg-white hover:bg-gray-200 rounded-xl p-2 text-black" href="#">
<svg class="inline text-black" xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#000" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>
Back
</a>
</div>
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8">
<div class="flex justify-center">
<img class="max-h-[300px] md:max-h-[500px]" src="{{ asset('images/purple-rocket.png') }}" alt="purple ship launching">
</div>
<div class="space-y-5">
<div class="mt-8 max-w-xl mx-auto">
<div class="px-8 pt-8">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs">{{ ship.status }}</p>
</div>
<h1 class="text-[32px] font-semibold border-b border-white/10 pb-5 mb-5">
{{ ship.name }}
</h1>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Spaceship Captain</h4>
<p class="text-[22px] font-semibold">{{ ship.captain }}</p>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Class</h4>
<p class="text-[22px] font-semibold">{{ ship.class }}</p>
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase">Ship Status</h4>
<p class="text-[22px] font-semibold">30,000 lys to next service</p>
</div>
</div>
</div>
</div>
{% endblock %}

Tip

If you copy the files (instead of the file contents), Symfony's cache system may not notice the change and you won't see the new design. If that happens, clear the cache by running php bin/console cache:clear.

I'm going to delete the tutorial/ directory entirely so I don't get confused and edit the wrong templates.

Ok, let's see what this did! Refresh. It looks beautiful! I love working inside a nice design. But... some parts are broken. In homepage.html.twig, this is our ship repair queue... which looks nice... but there's no Twig code! The status is hardcoded, name is hardcoded and there's no loop.

88 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<main class="flex flex-col lg:flex-row">
// ... lines 7 - 42
<div class="px-12 pt-10 w-full">
<h1 class="text-4xl font-semibold mb-8">
Ship Repair Queue
</h1>
<div class="space-y-5">
<!-- start ship item -->
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between">
<div class="flex justify-center min-[1174px]:justify-start">
<img class="h-[83px] w-[84px]" src="/images/status-in-progress.png" alt="Status: in progress">
<div class="ml-5">
<div class="rounded-2xl py-1 px-3 flex justify-center w-32 items-center bg-amber-400/10">
<div class="rounded-full h-2 w-2 bg-amber-400 blur-[1px] mr-2"></div>
<p class="uppercase text-xs text-nowrap">in progress</p>
</div>
<h4 class="text-[22px] pt-1 font-semibold">
<a
class="hover:text-slate-200"
href="#"
>USS LeafyCruiser</a>
</h4>
</div>
</div>
<div class="flex justify-center min-[1174px]:justify-start mt-2 min-[1174px]:mt-0 shrink-0">
<div class="border-r border-white/20 pr-8">
<p class="text-slate-400 text-xs">Captain</p>
<p class="text-xl">Jean-Luc Pickles</p>
</div>
<div class="pl-8 w-[100px]">
<p class="text-slate-400 text-xs">Class</p>
<p class="text-xl">Garden</p>
</div>
</div>
</div>
<!-- end ship item -->
</div>
// ... lines 80 - 84
</div>
</main>
{% endblock %}

Next: let's take our new design and make it dynamic. We'll also learn how to organize things into template partials and introduce a PHP enum, which are fun.