Using Surface for Layout - it is a hack but fun

Hi all,

I love surface and live_view! And I don’t love to write top level components with tailwind utility classes twice :slight_smile:. So I wanted to use Surface to define a layout. Maybe I missed it out, but I always read, that there is not solution yet and I have to wait for surface component/3 to be released. But I didn’t want to.
I had the idea, that a live view is a view and a view could be a layout. I tried it with a surface live_view as layout and the TopNavigation as a surface component:

defmodule Www do
  def live_view do
    quote do
      use Surface.LiveView, layout: {Www.LiveLayout, "live.html"}

      data top_menu_items, :list, default: []


  def component do
    quote do
      use Surface.Component



  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  defmacro __using__(which, opts \\ []) when is_atom(which) do
    apply(__MODULE__, which, opts)
defmodule Www.LiveLayout do
  use Www, :live_view

  alias Www.Ui.TopNavigation
  alias Surface.Components.LiveRedirect

  def render("live.html", assigns), do: render(assigns)

  def render(assigns) do
        <:brand><LiveRedirect to={ Routes.page_path(@socket, :index) }>Experiment</LiveRedirect></:brand>
          <TopNavigation.MenuItem :for={ {to, name} <- @top_menu_items } to={to}>{name}</TopNavigation.MenuItem>

      <main role="main" class="container">
        <p class="alert alert-info" role="alert" phx-click="lv:clear-flash" phx-value-key="info">{live_flash(@flash, :info)}</p>
        <p class="alert alert-danger" role="alert" phx-click="lv:clear-flash" phx-value-key="error">{live_flash(@flash, :error)}</p>


And it works!

Besides of sharing the solution, I wrote this post to get the opinion of the community about:

Is it a hack or is it supposed to work like that?

  • The fact, that I needed to write a render/2 function in the layout module makes it feel like a hack.

P.S. Later I needed the top_menu_items as assigned values, so it clearly becomes a hack. Obviously mount is not called on the layout - but it still works :joy:, so this post is only for sharing a small war story.

@ouven Thanks for using Surface!

I do not use live.html any more in my project. My live.html file contains only <%= @inner_content %>.

I now use the approach described here: Surface - A component-based library for Phoenix LiveView - #122 by Malian

Feel free to ask question if it is not clear! You can also join us on the slack channel :slight_smile: