Surface vs LiveView

Hi All.

I’ve started to look at Surface and it looks really good. I have a fairly complex LiveView app at the moment and I was considering tranitioning to Surface. I wondered if anyone with some experience could answer a couple of questions:

  • Are there any additional performance challenges with using Surface (above and beyond the standard LiveView ones).
  • Has anyone come across any “show stopper” with Surface?
  • Can I easily mix Surface and LiveView together (so drop down to LiveView where Surface might not be the correct answer).

Many thanks


1 Like
  1. AFAIK there should be none as Surface is mostly just compiler with some helper functions. This mean that it produces iovec the same as “regular” compiler.
  2. I do not use Surface right now, so I cannot tell.
  3. There should be no problems, as Surface component is still LV Component. In the same vain as Phoenix controller is still just a Plug and you can mix these as you please.

I recently added Surface to one of our LiveView apps and didn’t face any challenges. As @hauleth mentioned, Surface is only a compiler so the underlying components are LiveViews and your controller code doesn’t need to change at all.

That said, there is a useful @data macro for assigns that you could use. Since it’s pretty early days, you may find yourself building some components yourself (we have a custom table, panel and buttons).

I am not entirely sure how I feel about having part of the app in pure LiveView and part of it in Surface. I have decided to pause the migration and wait for the upcoming Phoenix.Component (EDIT).

1 Like

There is a big change coming with Liveview 0.16 with a new Html engine.

Some rewrite will be needed to support the new syntax.


Also the reason I paused. I would rather move to the canonical solution.

My understanding is that a Phoenix.Component can be used in both LiveView and eex templates although I can’t seem to recall where I picked this up.

1 Like

Correct! And Surface will likely just use Phoenix.Component too, so everything should be using the same abstractions.


Yes. It will use Phoenix.Component for sure. Using the same abstractions means you’ll be able to write components using Surface or HEEx and use them in any LV project. There are still some features we need to bring to LV itself to make that become a reality but we’re already working on it and we hope we can get there soon.


First of all thanks for Surface - it massively eased my path into LiveView because of the familiarity of components & Bulma.

Given Phoenix.Component, what would you say will become Surface’s USP? Why would one choose it over the built-ins? (Curious, not rude!)

1 Like

Surface is built upon LiveView, so it is not Surface vs LiveView, but really Surface vs leex. The biggest benefit to me is that Surface is more curated; it nudges you onto the right track without taking away functionality. leex is very powerful, but there are too many ways to shoot yourself in the foot.


Thanks everyone for the great answers. I’m definitely going to try out Surface now.




In addition to what @derek-zhou said, Surface comes with a lot of features that Phoenix Live View does not support (yet?). Here is a non-exhaustive list of these features:

Feel free to look at or join the Surface slack channel for more info :slight_smile:


Thanks for the exhaustive answer!

:if and :for definitely made it much nicer to build. Event handlers too. Yet to try some of the patterns.

Did find forms a bit easier to build in Surface than pure LiveView. Mainly because I haven’t fully wrapped my head around schemaless changesets.

I will join the channel and learn from what/how others are building.

Hi, I started migrating to surface last weekend. I haven’t migrated everything yet but it’s no problem including LV components in surface components. It was a little rocky at first figuring out how to do a few things like dynamic attributes and attributes with no value but there are several closed issues on the GitHub where people have asked the questions before and people are most helpful in the slack. I kept notes on what confused me and how to resolve it.

I much prefer it. Having the HTML parsed catches some bugs which were time consuming to find (which LV is getting soon too), I prefer typing {} to <%= %>, and the code feels tidier, descriptive and more structured with props, data, slots and surface syntax.



I would love to se some projects which used Surface. At the point where I tried to use it I found it problematic that I was having trouble adding it to .leex files.

Seeing @cmo answer, I would be interested to see the proper syntax!

As in, using your Surface components in .leex files? If you’re just using Surface, you change your .html.leex to .sface, otherwise the docs talk about using it in Phoenix templates here but I have not gone down that path.

If you want to see examples, check out:
puemos/radiopush: Create communities and share songs with Spotify users ( (old syntax)

code-shoily/covid19: A Phoenix app to display Novel Coronavirus (COVID-19) (


@msaraiva @derek-zhou

Maybe I’ve misread Surface entirely and in that case I apologize from the bottom of my heart :bowing_man:

otherwise I’m wondering whether you would have 1-2 best practices you’d recommend regarding apps using something like

Issues in particular:

  1. LAYOUT I’m making great strides into “componentizing” Adam’s wonderful design - and using Surface certainly adds to the fun :heart: (my elixir-fu hardly affords offering the result in a public repo like surface-tailwind, but I’ll let you be the judges of that shortly) but I’m very much questioning the use of a single ‘root’ element like yours in mail_liv.ex with 30-40 ‘if some action’ top-level components, and the live-controller-view-what-you-call-them would be humongous
def handle_params(_params, _url, %Socket{assigns: %{live_action: :set_password}} = socket) do
    user = System.get_env("USER")

      |> clear_flash()
      |> assign(
        info: "Set password of #{user}",
        page_title: "Set password of #{user}",
        password_hash: nil,
        saved_password: "",
        password_prompt: "Pick a password: ",
        buttons: []
  1. LINKS I’ve devoured your LIV repo, @derek-zhou and that seems to lean heavy on a kind of SPA concept but I’ve had a hard time finding the “links” where the user will LiveRedirect sort-of :slightly_frowning_face:

In the Liv repo I do not use live_redirect at all; only live_patch. I only have one liveview for the whole thing. I found this practice conceptually simple. It may or may not suit your need though.

I don’t think you are allowed to release a library of the tailwind UI components in some other framework, if that is your intention.

@cmo I acknowledge the implications but surely Adam will not take offense as long as any “implementation” merely identifies the components, like <Button> – I’m leaving the choice of classes entirely up to the integrator.

In fact I’m not planning on releasing anything as close to TailwindUI as surface_bulma is to Bulma.

But - point taken - and I will for sure contact Adam Wathan making sure that no IP is in any way infringed; I hold his work very dear!


@cmo you kind-a saved my ass on this one :icon_surprised: :+1:

The jury is in on the componentization of Tailwind, and the verdict is crystal

Thanks for reaching out. Unfortunately, packaging different Tailwind UI components like this, is somewhat creating a “re-distribution channel” for Tailwind UI.

We don’t have any sort of API for licence verification, and quite honestly don’t really want to at this point, so we can keep things simple in terms of how Tailwind UI is being distributed.

There is absolutely no problem in you creating your own little system to build apps with Tailwind UI within your licence rights, but this is likely not something you should be making public for other users to use.

Sorry about the potentially disappointing response, and thank you for your understanding!

On hindsight I reckon that I should have spent more time describing what “part” of Tailwind I am trying to componentize - but I guess by now it doesn’t matter; like my father used to say: No means no