KevinConti
How to toggle two sets of arbitrary tailwind classes for transitions?
I’m working with LiveView.JS for the first time. It’s one of the features that’s brought me back to LiveView on the frontend, and I feel like it was a critical piece that was missing for the community in earlier versions of the websocket-based paradigm.
By default, Phoenix comes installed with TailwindCSS. In Tailwind, transitions often occur via CSS classes. Take the following example:
<!-- When a menu is 'opened', it should receive the "block" class. When 'closed', it should receive the "hidden" class -->
<div id="mobile-menu" class="block sm:hidden">...</div>
<div id="desktop-menu" class="hidden sm:block">...</div>
This is a very common occurrence in Tailwind - transitions are almost exclusively driven via class addition and subtraction. When I went to try and apply this pattern with LiveView.JS, I was surprised to not see an obvious way to approach this.
The challenge with JS.toggle() is that the display property is directly applied via the style=display: attribute, where tailwind classes are invalid. This causes problems in the reactive example above, because the mobile menu could be toggled to be in the open state (style= display:block), and if the window is resized, this style will override the existing reactive behavior defined in tailwind (sm:hidden), causing both menus to be shown at once!
Of course, you could avoid this by using the JS.addClass and JS.removeClass functions, but that requires you to know the current state of the element to determine which function to call… meaning we’d need to keep state on the server, exactly what we are trying to avoid for something like a menu’s visibility!
I looked over the other functions available in LiveView.JS, but none seem to handle what I imagine must be an incredibly common use-case.
So, please help me out LiveView devs - am I missing something obvious? All I want is the ability to toggle two sets of arbitrary tailwind classes and let the state be contained entirely on the client via JavaScript. If I can do that, Tailwind will take care of everything else.
Thanks for your suggestions!
Most Liked
chrismccord
LiveBeats uses tailwind and toggles menu and such just fine from TailwindUI. Have you looked there?:
check the <.dropdown> in core components and the show_dropdown and hide_dropdown functions:
https://github.com/fly-apps/live_beats/blob/master/lib/live_beats_web/components/core_components.ex#L318-L336
al2o3cr
The post originally tried to use single backquotes on separate lines to quote a whole block of code, which resulted in the code not getting quoted. I’ve replaced them with triple-backquotes ``` which work as the author intended.
JohnnyCurran
Your example seems to be empty - was it a copy/paste that didn’t get into the post?
Also, have you seen JS.transition? It supports tailwind classes Phoenix.LiveView.JS — Phoenix LiveView v1.2.5
Last Post!
tubedude
I tried this at first and the problem I was getting was that the menu showing up cut in half.
(cut in half sidebar)
The issue was on the style: "display block;". So I figured I could transition the internal elements without actually tagging them to hide/show. Hence my initial code. But since you recommended I reverted back to show/hide and I tried all display options. The usual suspects (inline, inline-block) did not solve it, but using flex did the trick to show the sidebar correctly. But I decided not going this route, as I liked having one tag to control the show/hide state, leaving all others to transition only.
For reference the code would be:
JS.show(to: "#menu", transition: {some transition}, display: "flex")
And now on the original problem that hiding the menu was not working:
I added a transition bringing down the opacity just a notch (100% to 90%) to the main element and left it on screen being able to show its inner elements transition.
The final code is as follows:
defp close_mobile_menu() do
%JS{}
|> JS.transition(
{"transition ease-in-out duration-300 transform", "translate-x-0", "-translate-x-full"},
to: "#off-canvas-menu-mobile-inset",
time: 300
)
|> JS.transition(
{"ease-in-out duration-300", "opacity-100", "opacity-0"},
to: "#off-canvas-menu-mobile-button",
time: 300
)
|> JS.transition(
{"transition-opacity ease-linear duration-300", "opacity-100", "opacity-0"},
to: "#off-canvas-menu-mobile-backdrop",
time: 300
)
|>JS.hide(
transition: {"transition-opacity ease-linear duration-300", "opacity-100", "opacity-90"},
to: "#off-canvas-menu-mobile",
time: 300)
|> JS.set_attribute({"aria-expanded", "false"}, to: "#off-canvas-menu-mobile")
end
defp open_mobile_menu() do
%JS{}
|> JS.transition(
{"transition-opacity ease-linear duration-300", "opacity-0", "opacity-100"},
to: "#off-canvas-menu-mobile-backdrop",
time: 300
)
|> JS.transition(
{"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"},
to: "#off-canvas-menu-mobile-inset",
time: 300
)
|> JS.transition(
{"ease-in-out duration-300", "opacity-0", "opacity-100"},
to: "#off-canvas-menu-mobile-button",
time: 300
)
|> JS.show(
to: "#off-canvas-menu-mobile",
time: 300)
|> JS.set_attribute({"aria-expanded", "true"}, to: "#off-canvas-menu-mobile")
end
In other words, thanks @cmo for the push to the right direction.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










