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.
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.