Tailwind CSS
Me encanta utilizar Tailwind para CSS. Si nunca lo has usado antes, o quizá sólo hayas oído hablar de él, puede que... lo odies al principio. Esto se debe a que utilizas clases dentro de HTML para definirlo todo. Y así tu HTML puede acabar pareciendo, bueno, un poco loco. Pero dale una oportunidad. Yo me he enamorado de él. Y, en lugar de parecerme feo, me parece descriptivo.
Tailwind requiere construcción
Tailwind no es tu tradicional monstruo CSS en el que descargas un archivo CSS gigante y lo incluyes. En su lugar, Tailwind tiene un binario que analiza todas tus plantillas, encuentra las clases que utilizas y vuelca un CSS final que contiene sólo esas clases. Así mantiene tu CSS final lo más pequeño posible.
¡Pero para hacer esto, duh duh duh! Tailwind requiere un paso de compilación. Y no pasa nada. Que no tengamos un paso de compilación para todo nuestro sistema JavaScript no significa que no podamos optar por uno pequeño para un propósito específico.
Instalación de symfonycasts/tailwind-bundle
Existe un bundle superfácil para ayudarnos a hacer esto con AssetMapper. Se llamasymfonycasts/tailwind-bundle. Oye, ¡he oído hablar de ellos!
Baja aquí, ve a la documentación... y copiaré la línea composer require. Gira y ejecútalo:
composer require symfonycasts/tailwind-bundle
Este bundle nos ayudará a ejecutar el comando de compilación en segundo plano y a servir el archivo final. Lo apuntamos a un archivo CSS real, y luego colará el contenido dinámico. Ya lo verás.
Mientras estamos aquí, Ejecuta::
php bin/console debug:config symfonycasts_tailwind
para ver la configuración por defecto del bundle. Por defecto, el archivo que "construye" es assets/styles/app.css... ¡lo cual es genial porque ya tenemos un archivo assets/styles/app.css!
Para poner las cosas en su sitio, ejecuta un comando del bundle:
php bin/console tailwind:init
Esto descarga el binario de Tailwind en segundo plano, lo cual es genial. Ese binario es independiente y no requiere Node. Simplemente funciona. El comando también hizo otras dos cosas. Primero: añadió las tres líneas necesarias dentro de app.css:
| @tailwind base; | |
| @tailwind components; | |
| @tailwind utilities; | |
| // ... lines 4 - 8 |
Cuando se construya este archivo, se sustituirán por el CSS real que necesitamos. En segundo lugar, creó un archivo tailwind.config.js:
| /** @type {import('tailwindcss').Config} */ | |
| module.exports = { | |
| content: [ | |
| "./assets/**/*.js", | |
| "./templates/**/*.html.twig", | |
| ], | |
| theme: { | |
| extend: {}, | |
| }, | |
| plugins: [], | |
| } |
Esto le dice a Tailwind dónde buscar todas las clases que utilizaremos. Esto encontrará cualquier clase en nuestros archivos JavaScript o en nuestras plantillas.
Para ejecutar Tailwind, ejecuta:
php bin/console tailwind:build -w
Para ver. Eso construye... y luego se queda colgado, esperando futuros cambios.
Y... ¿qué ha hecho eso? Recuerda: ya estamos incluyendo app.css en nuestra página. Cuando actualizamos, ¡woh! ¡Se ve un poco diferente! La razón es que, si ves el código fuente de la página y haces clic para abrir el archivo app.css, ¡está lleno de código Tailwind! ¡Nuestro archivoapp.css ya no es exactamente este archivo fuente! Entre bastidores, el binario de Tailwind analiza nuestras plantillas y vuelca una versión final de este archivo, que luego devuelve. Este archivo ya contiene un montón de código porque llené las plantillas CRUD con clases Tailwind antes de empezar el tutorial.
Utilizar Tailwind
Pero veamos esto en acción de verdad. Si refrescamos la página, este es mi h1. Es pequeño y triste. Abre templates/main/homepage.html.twig. Enh1, añade class="text-3xl":
| // ... lines 1 - 4 | |
| {% block body %} | |
| <h1 class="text-3xl">Space Inviters: Plan your voyage and come in peace!</h1> | |
| {% endblock %} |
Ahora, actualiza. ¡Funciona! Si ese text-3xl no estaba antes en el archivo app.css, Tailwind acaba de añadirlo porque se está ejecutando en segundo plano.
Pegar el diseño
¡Así que Tailwind funciona! Para celebrarlo, vamos a pegar un diseño adecuado. Si has descargado el código del curso, deberías tener un directorio tutorial/ con un par de archivos. Mueve base.html.twig a plantillas:
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>{% block title %}Space Inviters!{% 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 %} | |
| {{ importmap('app') }} | |
| {% endblock %} | |
| </head> | |
| <body class="bg-black text-white font-mono"> | |
| <div class="container mx-auto min-h-screen flex flex-col"> | |
| <header class="my-8 px-4"> | |
| <nav class="flex items-center justify-between mb-4"> | |
| <div class="flex items-center"> | |
| <a href="{{ path('app_homepage') }}"> | |
| <img src="{{ asset('images/logo.png') }}" width="50" alt="Space Inviters Logo" > | |
| </a> | |
| <a href="{{ path('app_homepage') }}" class="text-xl ml-3">Space Inviters</a> | |
| <a href="{{ path('app_voyage_index') }}" class="ml-6 hover:text-gray-400">Voyages</a> | |
| <a href="{{ path('app_planet_index') }}" class="ml-4 hover:text-gray-400">Planets</a> | |
| </div> | |
| <div | |
| class="hidden md:flex pr-10 items-center space-x-2 border-2 border-gray-900 rounded-lg p-2 bg-gray-800 text-white cursor-pointer" | |
| > | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/><path d="M21 21l-6 -6"/></svg> | |
| <span class="pl-2 pr-10 text-gray-500">Search Cmd+K</span> | |
| </div> | |
| </nav> | |
| </header> | |
| <!-- Make sure the main tag takes up the remaining height --> | |
| <main class="flex-grow">{% block body %}{% endblock %}</main> | |
| <!-- Footer --> | |
| <footer class="py-4 mt-6 bg-gray-800 text-center"> | |
| <div class="text-sm"> | |
| With <svg class="inline-block w-4 h-4 text-red-600 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 3.22l-.61-.6a5.5 5.5 0 00-7.78 7.78l7.39 7.4 7.39-7.4a5.5 5.5 0 00-7.78-7.78l-.61.61z"/></svg> from Symfonycasts. | |
| </div> | |
| </footer> | |
| </div> | |
| </body> | |
| </html> |
Y estos otros dos al directorio main/:
| {% extends 'base.html.twig' %} | |
| {% block title %}Space Inviters!{% endblock %} | |
| {% block body %} | |
| <div class="flex"> | |
| <aside class="hidden md:block md:w-64 bg-gray-900 px-2 py-6"> | |
| <h2 class="text-xl text-white font-semibold mb-6 px-2">Planets</h2> | |
| <div> | |
| {{ include('main/_planet_list.html.twig') }} | |
| </div> | |
| </aside> | |
| <section class="flex-1 ml-10"> | |
| <form | |
| method="GET" | |
| action="{{ path('app_homepage') }}" | |
| class="mb-6 flex justify-between" | |
| > | |
| <input | |
| type="search" | |
| name="query" | |
| value="{{ app.request.query.get('query') }}" | |
| aria-label="Search voyages" | |
| placeholder="Search voyages" | |
| class="w-1/3 px-4 py-2 rounded bg-gray-800 text-white placeholder-gray-400" | |
| > | |
| <div class="whitespace-nowrap m-2 mr-4">{{ voyages|length }} results</div> | |
| </form> | |
| <div class="bg-gray-800 p-4 rounded"> | |
| <table class="w-full text-white"> | |
| <thead> | |
| <tr> | |
| <th class="text-left py-2">Purpose</th> | |
| <th class="text-left py-2 pr-4">Planet</th> | |
| <th class="text-left py-2">Departing</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for voyage in voyages %} | |
| <tr class="border-b border-gray-700 {% if loop.index is odd %} bg-gray-800 {% else %} bg-gray-700 {% endif %}"> | |
| <td class="p-4">{{ voyage.purpose }}</td> | |
| <td class="px-2 whitespace-nowrap"> | |
| <img | |
| src="{{ asset('images/'~voyage.planet.imageFilename) }}" | |
| alt="Image of {{ voyage.planet.name }}" | |
| class="inline-block w-8 h-8 rounded-full bg-gray-600 ml-2" | |
| > | |
| </td> | |
| <td class="px-2 whitespace-nowrap">{{ voyage.leaveAt|date('Y-m-d') }}</td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="flex items-center mt-6 space-x-4"> | |
| <a href="#" class="block py-2 px-4 bg-gray-700 text-white rounded hover:bg-gray-600">Previous</a> | |
| <a href="#" class="block py-2 px-4 bg-gray-700 text-white rounded hover:bg-gray-600">Next</a> | |
| </div> | |
| </section> | |
| </div> | |
| {% endblock %} |
| <ul> | |
| {% for planet in planets %} | |
| <li class="mb-4 group"> | |
| <a href="{{ path('app_planet_show', { | |
| 'id': planet.id, | |
| }) }}" class="block transform transition duration-300 ease-in-out hover:translate-x-1 hover:bg-gray-700 rounded"> | |
| <div class="flex justify-between items-start p-2"> | |
| <div class="pr-3"> | |
| <span class="text-white">{{ planet.name }}</span> | |
| <span class="block text-gray-400 text-sm">{{ planet.lightYearsFromEarth|round|number_format }} light years</span> | |
| </div> | |
| <img | |
| class="flex-shrink-0 w-8 h-8 bg-gray-600 rounded-full group-hover:bg-gray-500 transition duration-300 ease-in-out" | |
| src="#" | |
| alt="Image of {{ planet.name }}" | |
| > | |
| </div> | |
| </a> | |
| </li> | |
| {% endfor %} | |
| </ul> |
Actualizar. Huh, no hay diferencia. Eso es porque, al menos en un Mac, como moví y sobrescribí esos archivos, Twig no se dio cuenta de que estaban actualizados... por lo que su caché está desactualizada.
Abre una nueva pestaña del terminal y ejecuta:
php bin/console cache:clear
Entonces... ¡guau! ¡Bienvenido a Space Inviters! ¡Con estilo y listo para empezar! Pero las nuevas plantillas no tienen nada de especial. Tenemos una lista de viajes... pero todo es aburrido, código Twig normal con clases Tailwind.
Referenciar activos dinámicamente
Tenemos algunas imágenes de planetas rotas. Para arreglarlas, entra en el directoriotutorial/assets/... y mueve todos esos planetas a assets/images/. Elimina la carpeta tutorial/.
Esa etiqueta img rota proviene de la parcial _planet_list.html.twig. Aquí la tienes:
| <ul> | |
| {% for planet in planets %} | |
| <li class="mb-4 group"> | |
| <a href="{{ path('app_planet_show', { | |
| 'id': planet.id, | |
| }) }}" class="block transform transition duration-300 ease-in-out hover:translate-x-1 hover:bg-gray-700 rounded"> | |
| <div class="flex justify-between items-start p-2"> | |
| // ... lines 8 - 11 | |
| <img | |
| // ... line 13 | |
| src="#" | |
| // ... line 15 | |
| > | |
| </div> | |
| </a> | |
| </li> | |
| {% endfor %} | |
| </ul> |
¡Lo dejé para que lo termináramos! ¡Qué amable por mi parte! Sabemos que podemos hacer {{ assets() }}y luego algo como images/planets-1.png. Eso funcionaría. Pero esta vez, la parteplanet-1.png es una propiedad dinámica de la entidad Planet. Así que, en vez de eso, di ~ y luego planet.imageFilename:
| <ul> | |
| {% for planet in planets %} | |
| <li class="mb-4 group"> | |
| <a href="{{ path('app_planet_show', { | |
| 'id': planet.id, | |
| }) }}" class="block transform transition duration-300 ease-in-out hover:translate-x-1 hover:bg-gray-700 rounded"> | |
| <div class="flex justify-between items-start p-2"> | |
| // ... lines 8 - 11 | |
| <img | |
| // ... line 13 | |
| src="{{ asset('images/'~planet.imageFilename) }}" | |
| // ... line 15 | |
| > | |
| </div> | |
| </a> | |
| </li> | |
| {% endfor %} | |
| </ul> |
Y... ¡bonito! Sí, ya sé que la Tierra y Saturno no tienen ese aspecto -tengo algo de aleatoriedad en mis instalaciones-, ¡pero es divertido verlos!
Uso de KnpTimeBundle
Ya que el día 6 es el día de "hacer que todo parezca increíble", vamos a hacer dos cosas más. Para empezar, no me encanta esta fecha. Es aburrida Quiero una fecha con un aspecto genial.
Así que instala uno de mis bundles favoritos:
composer require knplabs/knp-time-bundle
Esto nos proporciona un práctico filtro ago. Así que en cuanto esto termine, gira y abrehomepage.html.twig. Busca leaveAt y ya está. Sustituye ese filtro date por ago:
| // ... lines 1 - 4 | |
| {% block body %} | |
| <div class="flex"> | |
| // ... lines 7 - 13 | |
| <section class="flex-1 ml-10"> | |
| // ... lines 15 - 29 | |
| <div class="bg-gray-800 p-4 rounded"> | |
| <table class="w-full text-white"> | |
| // ... lines 32 - 38 | |
| <tbody> | |
| {% for voyage in voyages %} | |
| <tr class="border-b border-gray-700 {% if loop.index is odd %} bg-gray-800 {% else %} bg-gray-700 {% endif %}"> | |
| // ... lines 42 - 49 | |
| <td class="px-2 whitespace-nowrap">{{ voyage.leaveAt|ago }}</td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| // ... lines 56 - 59 | |
| </section> | |
| </div> | |
| {% endblock %} |
Y... ¡mucho más chulo!
¿Qué más? Echa un vistazo a las áreas CRUD. Éstas se generaron mediante MakerBundle... pero... Las precargué con clases de Tailwind para que tuvieran buen aspecto. Vaya, hay mucho que celebrar ahora mismo. No me quejo.
Pero... si vas a un formulario, ¡se ve fatal! ¿Por qué? El marcado del formulario proviene del tema de formularios de Symfony... que genera los campos sin clases Tailwind.
Ejemplos de Flowbite para Tailwind
Entonces, ¿qué hacemos? ¿Tenemos que crear un tema de formulario? Afortunadamente, no. Una de las cosas buenas de Tailwind es que hay todo un ecosistema creado a su alrededor. Por ejemplo, Flowbite es un sitio con una mezcla de ejemplos de código abierto y funciones profesionales de pago. En la parte de código abierto puedes encontrar, por ejemplo, una página de "Alertas" con diferentes marcas para crear alertas de gran aspecto. Y no necesitas instalar nada con Flowbite. Todas estas clases son nativas de Tailwind. Puedes copiar este marcado en tu proyecto y actualizarlo. Nada especial. Y me encanta.
Esto también tiene una sección de formularios donde muestra cómo podemos hacer que los formularios tengan un aspecto realmente bonito. En teoría, si pudiéramos hacer que nuestros formularios salieran así, tendrían un aspecto estupendo.
Añadir un tema de formulario Tailwind
Y afortunadamente, hay un bundle que puede ayudarnos. Se llamatales-from-a-dev/flowbite-bundle. Haz clic en "Instalación" y copia la línea composer require. Luego ejecútalo:
composer require tales-from-a-dev/flowbite-bundle
¡Hoy nos van a instalar un montón de cosas! Nos pregunta si queremos instalar la receta contrib. Digamos que sí, permanentemente. ¡Genial!
Volviendo a las instrucciones de instalación, no necesitamos registrar el bundle -eso ocurre automáticamente-, pero sí necesitamos copiar esta línea para el archivo de configuración de tailwind.
Abre tailwind.config.js, y pega esto:
| /** @type {import('tailwindcss').Config} */ | |
| module.exports = { | |
| content: [ | |
| // ... lines 4 - 5 | |
| "./vendor/tales-from-a-dev/flowbite-bundle/templates/**/*.html.twig", | |
| ], | |
| // ... lines 8 - 11 | |
| } |
Este bundle viene con su propia plantilla de tema de formulario con sus propias clases Tailwind, así que queremos asegurarnos de que Tailwind las ve y genera el CSS para ellas.
El último paso en los documentos es decirle a nuestro sistema que utilice este tema de formulario por defecto. Esto ocurre en config/packages/twig.yaml. Lo pegaré... y luego arreglaré la sangría:
| twig: | |
| // ... line 2 | |
| form_themes: ['@Flowbite/form/default.html.twig'] | |
| // ... lines 4 - 8 |
Ya está. Vuelve atrás, actualiza y ¡eureka! En poco más de 10 minutos, instalamos Tailwind y empezamos a utilizarlo en todas partes.
Mañana volveremos a JavaScript y aprovecharemos Stimulus para escribir código JavaScript fiable que nos encante.
59 Comments
Hi !
Thank you very much for your work.
To get tailwind completion working in twig template with PhpStorm :
PhpStorm documentation
After step 2
npm install -D tailwindcssYou don't need to do step 3 because
tailwind.config.jsfile already exists (aftertailwind:init),but you need to add
"twig": "html"to theincludeLanguageslist in IDE "Settings" -> "Languages & Frameworks" -> "Style Sheets" -> "Tailwind CSS"That's kind of a non-sense. We want to avoid installing node, but we need it just for completion...
Hey AntoineRm,
Agree, that's not ideal, though at least you need it only for localhost where you're coding... no need of NodeJS on prod, that is still good. Or if you don't care about autocompletion - you can just ignore it.
Cheers!
Hey ThierryGTH,
Thank you for sharing this information with others!
Cheers!
I just did this, and the tutorial shows this:
but I get an error about a missing Flowbite namespace.
Doing a
bin/console debug:twigshows:which makes sense.
Changing '@Flowbite' to '@TalesFromADevFlowbite' makes it work.
Change since making this tutorial? (The world moves fast :wink:).
Or did I miss an update somewhere?
Lol, yup - a chance since yesterday! https://github.com/tales-from-a-dev/flowbite-bundle/pull/20
Their docs are already updated, but I'll add a note to the tutorial.
Thanks!
FOR THOSE WANTING TO INSTALL FLOWBITE
Some Flowbite components require custom javascript to work as intended. It is also part of the installation instructions for the flowbite-bundle. So if you are using that bundle, like this course does, I would highly recommend Flowbite. The link above links to the install instructions for Symfony. However, they aren't quite what we need when using Asset Mapper, but they are close.
1) Make sure you have node/npm installed on your local machine.
2) Install Flowbite with
npm install --dev flowbite(or the equivalent command for whatever node package manager you prefer)3) Update your
tailwind.config.js:4) Restart
symfony console tailwind:build -w5) Install Flowbite again for asset mapper use via
symfony console importmap:require flowbite6) Update
app.jsto import Flowbite js:I tested this out with their accordion component and it seemed to be working as intended.
One issue you may notice if doing this along with the course code is that the form theme will not match the dark styling of the site. You can resolve this by adding the
darkclass to the body of the base template. Tailwind, Flowbite, and the Flowbite Bundle all support light and dark mode. Adding thedarkclass applies any dark mode specific styles to its children.@ik3rib asked a question regarding using the flowbite-datepicker package. I tried to figure this out for a little while, but couldn't get it working.
Aside
Developing an app without Tailwind is not a pleasant thought for me, so I'm glad this course is covering how to use it. It is disappointing that it requires a build step though, since as I see it, the whole point of this stack, and asset mapper in general, are to avoid that. As soon as we have to add a build step, albeit a small one, it starts to negate the point of using the stack. However, at this point I can accept adding a small build step requirement as it is a necessary evil for an essential tool.
With that said, once you start having to install node packages separate from asset mapper (via npm, yarn, etc.), this stack starts to loose a lot more value for me. Especially when you consider that we have to use a
require()statement in the tailwind config file, which is not really a valid command in our stack. But it works because like @weaverryan mentioned in an earlier comment, the tailwind binary is a wrapped node app. So it works.. buuut it adds additional mental overhead when trying to reason about the application setup. Every time you see that squiggly you have to remember why therequire()is not erroring out there, but would elsewhere.I do love the spirit of this stack though, and will continue learning more about i! Even though I have some concerns with it, that is the case with every other front end framework I've worked in 😅 Mostly the JS world is just so much messier and hard to keep up with when compared to PHP and Symfony. Thanks for the work on this course and everyone's work on everything Symfony-related!
Hey @jdevine!
Thanks for posing this! Because of this - and similar questions from others - I'm adding a bonus chapter at the end about Flowbite JavaScript - https://symfonycasts.com/screencast/last-stack/flowbite
But the tl;dr:
flowbiteJavaScript (and I do in the bonus chapter), but we don't use it for Symfonycasts - and in my perfect world, we're all sharing tiny Stimulus controllers to handle things like drop-downs, instead of including big JS libraries like Flowbite.Cheers!
Thanks Ryan! I'll check that out.
The main reason I wanted to include the flowbite js was because it is mentioned as a requirement in the flowbite-bundle used in this lesson. Personally, I use flowbite for reference to create templates at work, but don't use their js. Although the reason in that case is because it conflicts with some of our legacy front-end code. So we just build our own interactive functionality.
However, I think the two areas in frontend that are worth including third party (possibly large) js libraries are tailwind and flowbite (or a similar component library). It is a huuuuuge pain in the butt the constantly have to rewrite functionality for something like combobox, especially if you want to get all the accessibility stuff right. It would be nice if there was a bundle that provided flowbite components, but with the interactivity built with stimulus. I'm not sure what that would look like, as I am not as familiar with stimulus. But if it's possible, maybe that's something I'll work on building.
Hey @jdevine!
Ah, all the more reason for us to have the extra, clarifying chapter then :)
💯
💯 again! This year, I'd like to have a place where we can grab "UI components" (i.e. HTML markup + Stimulus controllers or even a Twig component instead of direct HTML markup) and put them into our project. I think these would be copy-pastable things, though I think we should try to package and use reusable Stimulus controllers whenever possible (e.g. you might copy some HTML markup for a popover, then run the
importmap:require stimulus-popoverto get that reusable Stimulus controller). Heck, maybe there is even a little script -bin/console ui:install popover- to grab the stuff for you. I've been bouncing this around in my head since Oct and the value continues to be reinforced. I'd love to know what would be most useful for you. And, I would love to collaborate ;) - having more people with more real-world needs would help a lot.Cheers!
Glad to hear you thinking something like that is worth building too. In both my professional work and personal work I like the idea of being able to grab a premade component as a complete package (markup, styling, and interactivity), or some subset of those options.
It probably makes most sense to just provide a kind of "headless" component like you mentioned where it just installs the controller (logic).
In a dream scenario, a user could even install a full-blown component with a command
bin/console ui:install popoverlike you mentioned, but with some additional optional parameters. For the sake of this example, I'm going to say the defaults for this command install the stimulus controller and an unstyled twig template with the necessary html and variables (basically functioning as props in other frontend frameworks). A parameter like--headlessor--controller-onlywould only install the stimulus controller. Another parameter--theme THEME_NAMEwould install the component, twig template, and provide styling based on preset themes (Flowbite, DaisyUI, Bootstrap, etc.) In terms of templates, perhaps this could draw inspiration from thesymfony/formpackage.This is just a first off the cuff reaction to imagining how a user might want to work with these components. I haven't given a whole lot of consideration to the architecture, but have a vague idea of how it might work. Although, I don't have experience building reusable Symfony bundles. It seems like the "headless" part of it is definitely doable, the template part isn't as clear. And even if the template part works, it would be considering whether Symfony would want to actually maintain that piece or hand it off to individual bundle developers. I would argue that it would be worth maintaining, because in my opinion, it is the one missing piece to making Symfony a truly all-in-one solution (well.. aside from probably still needing Tailwind). At the same time, it would only be worth it if there was a good chance it would be a long-living solution and unlike so many frontend frameworks/libraries that come and go.
This granularity of this solution would be great because it could be used for new projects to get up and running quickly, or in a legacy codebase (which is my situation). Vue and React are great options for a lot of cases, but a massive headache to start to implement into an existing legacy codebase (although I have done that with Vue via the stimulus bridge at work).
Not sure what your thoughts are on any of that. I would be more than happy to collaborate on something like that or whatever you had in mind.
Hey @jdevine!
Thanks for the detailed thoughts! We've started working internally on this... I'm not sure when we'll have something to show, but it'll likely show up on the symfony/ux repository. Open questions are:
A) Exactly how Flexible we make this. Initially, it will probably be Tailwind only. But it could be feasible to have an unstyled version or a version where the CSS is also provided (but not via Tailwind).
B) We are definitely going to give these to the developers and not maintain them. Especially for HTML markup, devs will need to customize. But for Stimulus controllers, if it's large enough, those could be turned into their own packages.
Anyway, hopefully we'll open up the work soon and people can collaborate - not quite there yet!
Cheers!
There have been some changes in tailwind since this tutorial came out. In addition to add the class 'dark' to body (thanks to jdevine for his comment), you also must add the line
to the tailwind.config.js. It should look like
Thanks for the great tutorial and helpfull comments!
This is not working for me, what is changed since then?
Hey Omer,
Since Tailwind CSS v4 that
tailwind.config.jsis redundant now and you should declare your variants in the CSS:And then make sure you have
<body class="dark">to apply the dark theme.I hope that helps!
Cheers!
[ERROR] Tailwind CSS init failed: see output above.i tried to isnstall tailwind and during installation i ran into a timeout. after the timeout i could not run:
php bin/console tailwind:initwithout hte error above.
deleting the
var/tailwindfolder did the trick ...Hey @Ek24
That's unexpected. I wonder if you have a time limit to execute scripts on your local machine. Anyway, Thank you sharing your solutions
Cheers!
With Symfony 7, I have no completion in my twig files with PHPstorm ( it works with VSC). I did add "twig": "html" to the includeLanguages list but nothing change. I have to work in VSC because it's really not convenient to work without completion.
Edit: After hours of research, it seems that it's a very old issues of JetBrain IDE. The only way is to install tailwind with npm. Now that symfony push for avoiding node, i hope they will take care of that, or every tailwind lover will switch to VSC.
Hey AntiineR,
Glad to hear you were able to find the solution. And thanks for sharing it with others.
Yeah, I hope that issue will be pushed forward too!
Cheers!
Yes. I will just install npm in local to get the completion.
I'm unable to set dark mode with flowbite forms with tailwind-version-4. I tried to set in the main body tag class = "dark" and in the tailwind.config.js
darkMode: 'selector',, but those didn't do the trick.Hey Omer,
Yeah, in Tailwind v4, dark mode is no longer configured in
tailwind.config.js. Having that file with Tailwing v4 is not the best practice anymore. Now you can do it in the CSS file where you import Tailwind CSS and Flowbite:And after this make sure your CSS is recompiled and try to add dark CSS class to the body tag:
In short, you should declare your custom variants directly in the CSS using special tag instead of doing it in
tailwind.config.jsas it was in Tailwind v3.I hope that helps!
Cheers!
Hey Victor,
Indeed I tried this with version 7.4 Symfony and the tailwind.config.js is not used. Is there a way to get @plugin "@flowbite/plugin"; with assetmapper or are node_modules inevitable. What I did now is get default.css file from the flowbite repository and include this in my app.css and that worked but I dont understand why the tales-form-a-dev is not including this css file.
note: Im considering learning webpack-encore
Hey Omer,
First of all, we have a bonus chapter about Flowbite at the end of this course: https://symfonycasts.com/screencast/last-stack/flowbite - I would recommend you to check it in case you're interested in Flowbite integration. I hope that will help you with some possible questions. I personally don't use Flowbite much, so it's hard to me to give you decent advice on it.
I think the short answer is yes, Node modules are inevitable if you want Tailwind plugins like Flowbite to work as plugins, because those plugins are not just CSS. What you did (copying default.css) works only because you bypassed the plugin system entirely.
Webpack Encore should be a good option for solving this. And we have a separate coruse about it here: https://symfonycasts.com/screencast/webpack-encore . Or you can take a look at the modern Vite.js tool instead, which has become popular lately, though we still don't have a course about it yet.
Cheers!
Hello when i loop over "planets" i realize the array is empty, was there a step before to populate the db ?
Hey Yoyo,
Did you load the fixtures? :) When you download and unzip the course code - you will find the README.md file inside with all the instructions you have to do. On of them will be load the fixtures with:
symfony console doctrine:fixtures:loadcommand. Just run this command and try again ;)Cheers!
Hi. I looked all over and couldn't find a fix this
composer require symfonycasts/tailwind-bundle./composer.json has been updated
Running composer update symfonycasts/tailwind-bundle
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
In VersionParser.php line 191:
Invalid version string "9999999.9999999" `
I also tried
composer require symfonycasts/tailwind-bundle:dev-mainbut the same result!
Hey Valentin,
That's kinda weird. Try clearing composer's cache
composer clear-cacheand even upgrading composercomposer self-upgradeif that doesn't work, try reinstalling your vendors
Cheers!
Hi!
Thank you for the course)
Have a question about Tailwind 4 setup in the context of symfonycasts bundle.
According to Tailwind docs, the config file
tailwind.config.jsis not required for the version 4 and above. Andbin/console tailwind:initcommand has the next output:So, how to deal with flowbite config in this situation? Where the flowbite templates path should be added if
tailwind.config.jsdoesn`t exists?Thanks.
Hey @Olexandr,
In Tailwind 4, you use the
@plugindirective directly in your CSS file. See the Flowbite getting started guide to see how this works.Alternatively, you can still use the v3
tailwind.config.jsbut now you define it in your CSS file also, with the@configdirective.Hope that helps!
Kevin
The simplest solution is to set a previous version of Tailwind in
symfonycasts_tailwind.yaml:Thank you, @kbond.
When I use Flowbite and want to edit a planet, the error appears: Unknown "tailwind_merge" filter
Hey @giorgiocba!
Is it a PHP/Twig error?
I believe installing this package will solve. It adds the
tailwind_mergefilter.Let me know if that doesn't help.
--Kevin
Problem solved. Thank you very much!
When I went to use the SymfonyCasts/tailwind-bundle in an API Platform project using docker containers it took me a while to discover that I had to go into the docker container and run the
bin/console tailwind:buildcommand inside the docker container because running it at the WSL(host operating system) level wasn't updating the vendor folder inside the container because the compose.override.yaml has a volume entry for- /app/varthat causes the var directory to not map.Perhaps the documentation at https://symfony.com/bundles/TailwindBundle/current/index.html#watch-mode-in-docker-with-windows-host could be augmented to help diagnose that issue as it only covers situations where the Windows /var directory is mapped into the docker container. It could also be a place to add instructions for modifying a docker compose.yaml file to include the proper watch mode usage of the
bin/console tailwind:buildcommand for dev and the compile version for production.The error message one gets if they are in this situation is "Built Tailwind CSS file does not exist". Which is true in the docker container, even if the file exists outside of the docker container.
Hey @Jason-Aller
Thank you for the detailed explanation, and I agree that this could be mentioned in the Symfony docs. Have you considered proposing a PR?
https://github.com/SymfonyCasts/tailwind-bundle/edit/main/doc/index.rst
Cheers!
I tried to use complete updated software (including Symfony 7) for these casts, but then as it comes to this module where we have the first access to DB, DB access fails complete. It is that the whole zenstruck part is depecreated and not working anymore with zenstruck/foundry >= ^2. I do not see any database item at all.
Hey @astrodev,
Yeah, foundry 2 added a bunch of breaking changes - there is a migration path for 1.x-2.x you can take a look at. In the meantime, no problem staying on foundry 1.x for this tutorial.
My form looks slightly different than in the video:
E.g.
var(--tw-text-opacity)seens to be1, so thecoloroflabelandinputelements isrgb(17, 24, 39)(dark color).var(--tw-bg-opacity)also seems to be1, which make thebackground-colorforinputelementsrgb(249, 250, 251)(light color).E.g.
dark:text-whitedoes not seem to have an effect on thelabelelements.Any idea what the reason could be?
Okay, as soon as I configure Firefox to use dark mode the form looks the same. But it looks broken when I use light mode in Firefox.
You got it :). I should have mentioned this. I didn't style for light mode. So if you ARE in light mode, then everything still looks dark (from my design) except for the form elements, which are properly styled for both. So you get "light mode" forms... on my dark background.
good morninig all, i don't have any idea, why this error showed up
In bb90e00c589eb640d305e9be5c5614e2.php line 1295:
syntax error, unexpected variable "$__internal_6f47bbe9983af81f1e..."Hey Ayoub,
What PHP version are you using? For this project you need PHP 8.2+. Also, try to clear the cache, i.e. manually remove it with
rm -rf var/cache/and try again. Does it help? The file you linked sounds like a cached file, right? So cache clear should probably help.Cheers!
thank you
i fix it already with the composer.json i put this versions in my composer and i run
composer updatethank you again
Thanks for sharing it with others!
Cheers!
Hello Ryan! Amazing course!
I'm all new to php frameworks and your lessons are really valuable, thanks a lot!
Talking about tailwindcss, it supports plugins, like daisyui. I can't make tailwind find daisyui source code when i require the pack using "symfony console importmap:require daisyui". It's because symfony's asset mapper installs the lib inside "assets/vendor/daisyui" and tailwindcss search for plugins in the "nome_modules/" folder, which is only created thru npm install.
What's the recomended approach reagaarding this issue?
Thank you!
Tarik Tarilonte.
Yo @Tarik!
Thank you! This is a fun one!
I haven't had a chance to try it yet, but I believe (i.e. I have been told by one person), that you can accomplish plugins with Tailwind (using the standalone binary that the bundle downloads) by exactly following the existing documentation. So, for Daisy - https://daisyui.com/docs/install/ - you should be able to do exactly what they show:
A)
npm i -D daisyui@latestB)
plugins: [require("daisyui")],intailwind.config.jsThe standalone
tailwindbinary we download is a Node app... but it's wrapped up in a way that makes it standalone and not require node. But then if you need to add a 3rd party plugin to it, then you should still install it via node. And that's ok! Not needing to use node/npm is nice, but it's also totally great to be able to use it to grab something you need.Btw, if you try this and it works, will you let me know? It's something we will need to add to the TailwindBundle documentation to help others.
Cheers!
Hi Ryan! Hope all is well! Been praying for you.
I have installed the daisyui plugin as suggested above. And it works great! Thanks so much for helping with this!
I do have another question, though. Is there a way to pass in a config? I tried and had to format it different, but I don't thinks it's taking effect - or rather, I don't think the config is being passed to the plugin. I've just started with this so, of course, it's likely that I simply don't know how to check if the config is working or not ;-) I tried to exclude the "button" and the btn classes still worked.
This is the format that passed the tailwind build process (the excludes were only for testing):
`
`
If you have some time, any help with this would be greatly appreciated.
Again, thanks for all the videos and helpful comments!
I haven't tested it yet, but I believe I've found the solution.
https://v4.daisyui.com/docs/config/
Here's their sample configuration:
`
`
The reason is that the tailwind-bundle defaults to Tailwind version 3, but the docs for DaisyUO assumed Tailwind version 4. However, v4 of DaisyUI assumes Tailwind version 3.
Hey @JeffJones
You have a small detail in your config, move your config options outside the
pluginskey. Like this:Cheers!
"Houston: no signs of life"
Start the conversation!