Problem
Users of Tailwind CSS know the productivity gains the utility-first CSS framework provides. One of Tailwind’s biggest advantages is that you can rapidly build applications without ever leaving your HTML. There’s no context-switching between markup and CSS files, or searching for where classes are or aren’t defined.
You almost never have to touch individual CSS files – except when you want to customize the styling of the classes that Phoenix LiveView uses for loading indicators and form feedback. Yuck.
Worse than leaving your markup to trudge through CSS files, is the strict nature of the Phoenix LiveView classes that make them hard to customize without many one-off CSS rules.
Fortunately, Tailwind has a plugin feature that solves this beautifully.
Solution
Phoenix LiveView uses the following CSS classes to provide user feedback:
phx-no-feedback
- applied when feedback should be hidden from the userphx-click-loading
- applied when an event is sent to the server on click while the client awaits the server responsephx-submit-loading
- applied when a form is submitted while the client awaits the server responsephx-change-loading
- applied when a form input is changed while the client awaits the server response
Customizing each of these for each scenario, breakpoint, desktop and mobile size, etc, gets cumbersome and error-prone. We can sidestep these issues by defining a Tailwind plugin which provides variants for each of these Phoenix LiveView specific classes inside your assets/tailwind.config.js
file:
const plugin = require('tailwindcss/plugin')
module.exports = {
content: [
"./js/**/*.js",
"../lib/*_web.ex",
"../lib/*_web/**/*.*ex"
],
theme: {
extend: {},
},
plugins: [
require("@tailwindcss/forms"),
plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &'])),
plugin(({addVariant}) => addVariant('phx-click-loading', ['&.phx-click-loading', '.phx-click-loading &'])),
plugin(({addVariant}) => addVariant('phx-submit-loading', ['&.phx-submit-loading', '.phx-submit-loading &'])),
plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &']))
]
}
We added four plugin
definitions which add variants for each Phoenix LiveView class. The &.phx-click-loading
notation specifies that the variant is applied when an element directly has the phx-click-loading
class applied. Additionally, the .phx-click-loading &
notation tells Tailwind to also apply the variant to children of a container with the phx-click-loading
class.
This allows us to customize our markup like so:
<button
phx-click="send"
phx-disable-with="Sending..."
class="p-4 rounded-lg bg-indigo-600 phx-click-loading:animate-pulse"
>
Send!
</button>
We style our button with the regular Tailwind classes, then we customize which Tailwind classes are applied on phx-click
. We do this by simply prefixing the Tailwind classes by phx-click-loading
, such as phx-click-loading:animate-pulse
to show animation feedback while the server processes our event. Here’s what it looks like in action:
You can expand this to apply red borders to form inputs when the inputs are invalid, or hide and show input errors – all without leaving your markup. Since elements are all stylized inline, you can also customize your Phoenix LiveView feedback on a case-by-case basis. Happy hacking!