Home Posts Post Search Tag Search

LiveView 22 - Chapter 7: Your Turn
Published on: 2026-01-10 Tags: elixir, Blog, Side Project, State, LiveView, Html/CSS, Phoenix
• Build a component that toggles a button showing either + expand or - contract, and then marks a 
corresponding div as hidden or visible. Under what circumstances would you use a CSS style with 
display: none, versus rendering/removing the whole div? Hint: think about how many bytes LiveView 
would need to move and when it would move them.

Okay so for this I created a new module that was within the components folder so I could leverage the live_component framework. I needed to also take advantage of the fact that I wanted the component to manage it’s own state so that I could have a value for the state :expanded create this file pento/lib/pento_web/live/components/toggle_panel.ex

defmodule PentoWeb.Components.TogglePanel do
  use PentoWeb, :live_component

  @impl true
  def mount(socket) do
    {:ok, assign(socket, expanded: false)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="toggle-panel">
      <button phx-click="toggle" phx-target={@myself}>
        {if @expanded, do: "▲ Product Image", else: "▼ Product Image"}
      </button>

      <div style={"display: #{@expanded && "block" || "none"};"}>
        {render_slot(@content)}
      </div>
    </div>
    """
  end

  @impl true
  def handle_event("toggle", _params, socket) do
    {:noreply, assign(socket, :expanded, !socket.assigns.expanded)}
  end
end

Then to use it you will need to add something like this. Keep in mind that as we have a component that is not designed to handle different divs or sections we will need to all be within the initial structure of the .live_component

<.live_component module={TogglePanel} id="product-image">
    <:content>
        <img width="200" src={image_url(@product)} />
    </:content>
</.live_component>

Finally to answer the question we have 2 ways of getting rid of a section: display:none or removing the div. I choose the display none as I think that any time you would allow the user to toggle a portion they will toggle it back and at least the none will only really effect the users computer. If you were to remove the div it would require the refresh of the entire page as its a new html that would need to be rendered.

The second case might be something that a user would want toggled to get a better responsiveness from the page after the refresh. Maybe something that is requiring a lot of processing power on older machines so we offer a more simplified page.

• Bonus: Consider the downside of implementing a common JS interaction—like showing and hiding an 
element—with a live component. With a live component, you’re making a round-trip to the server to do 
something you could easily do purely on the client-side. Check out the docs here on LiveView’s JS 
Commands and use them to refactor your live component into a stateless one. Use JS Commands to 
implement the “show/hide” functionality without round-tripping to the server.
defmodule PentoWeb.Components.TogglePanelJS do
  use PentoWeb, :live_component

  @impl true
  def render(assigns) do
    ~H"""
    <style>
  .hidden { display: none; }
  .fade-in { animation: fadeIn 0.2s forwards; }
  .fade-out { animation: fadeOut 0.2s forwards; }

  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
  @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
</style>
    <div class="toggle-panel">
      <button
        phx-click={JS.toggle(to: "#panel-#{@id}", in: "fade-in", out: "fade-out")}
        type="button"
        class="arrow-button"
      >
        ▼
      </button>

      <div id={"panel-#{@id}"} class="hidden">
        <%= @content %>
      </div>
    </div>
    """
  end
end
<.live_component
  module={PentoWeb.Components.TogglePanelJs}
  id="product-image"
  content={~H"<img alt='product image' width='200' src={image_url(@product)} />"}
/>

As you can see here we need to render the entire portion of the code into one block where all the needed values are within the component.