It has ways to indicate "things are in flight" but for distant deployments you probably still need to write JS to manage some in-page elements for reasonable feeling UX.
This isn't hard, it has support for interop with plain JS or existing libraries and a somewhat limited built in system for basic show/hide/set/remove attributes (somehow lacks a "toggle_class" and "toggle_attribute" function though so you can write those yourself...). You can dispatch any custom JS event from it to cover .. anything you need.
If you're exclusively deploying to on-site/one-site/geographically-constrained systems, then you actually skip that issue...
You also have to re-think about how write code as Elixir is immutable, though I see that as a bonus.
Phoenix (and by extension LiveView) are actually a lot smaller than you might think coming from Rails or the like, mostly leveraging existing tools.
Elixir/Erlang/BEAM make building systems incredibly pleasurable. It ships with many tools to make and connect complicated things together, in a reliable way. Depending on what you make you may find it very liberating after working with Node.
Most JS UIs behave like this: open a list view, watch a spinner until data is fetched from the server. There is practically no difference between that and showing a spinner first and then the list.
So you need to pay some attention to applying-css/hooking-into-the-tooling (phx-click-loading) to show that a button was indeed clicked and is working on fetching data.
Some things such as flipping between tabs can feel quite bad if you click and the tab-button just puts a spinner there or the button just "fades a bit". Its much nicer to replace the content pane with some mock-blocks to show "yes, the tab swapped and its loading data" which is where you need JS to temporarily hide/show some things.
And once you start doing that you should really be measuring the avg rtt on the socket because showing the mock-content for 1ms then swapping the real content is also bad, so you need JS again to figure the best UX for that client.
Or you simply render all the tabs in one go and again, duck into JS to perform the local show/hide.
You do not need to write that JS though. LiveView comes with a few helpers to assist in client-only features.
I don't remember the exact circumstances from when I last dabbled with LiveView, but I recall that using a combination of CSS delay (so that fast connections wouldn't see a useless spinner) and an opacity transition style (to reduce jarring elements popping up suddenly) produced a nice effect, all with plain CSS and a phx-loading attribute.
I was looking for something else after years in React ecosystem and came across Phoenix LiveView but I wanted to write in Typescript.
(Note: Am author)
[1] - https://LiveViewJS.com
The main limitation is likely offline apps, LiveView requires a persistent connect to the server. I doubt this is something you'll encounter for your use case.