Getting Started

Start building rich interactive user-interfaces, writing minimal custom Javascript now. For a quick start, you can either add Surface to an existing Phoenix LiveView project or install it from scratch.


Phoenix v1.6 comes with built-in support for LiveView apps. You can create a new phoenix application with:

mix my_app

Note: In case you want to add Surface to an existing Phoenix application that doesn't have LiveView properly installed, please see Phoenix Liveview's installation instructions at

Add surface to the list of dependencies in mix.exs:

def deps do
    {:surface, "~> 0.6.0"}

Configuring the project using mix surface.init

After fetching the dependencies with mix deps.get, you can run the surface.init task to update the necessary files in your project.

In case you want the task to also generate a sample component for you, use can use the --demo option. A liveview using the component will be available at the /demo route.

Additionally, the task can also set up a Surface Catalogue for your project using the --catalogue option. The catalogue will be available at /catalogue.

Note: When using the --demo and --catalogue options together, the task also generates two catalogue examples and a playground for the sample component.

mix surface.init --demo --catalogue

Start the Phoenix server with:

mix phx.server

That's it! You can now access your application at http://localhost:4000.

You can see the full list of options provided by surface.init running:

mix help surface.init

Configuring the project manually

In case you don't want to use mix surface.init, you can configure the project manually with the following steps.

If you're using mix format, make sure you add surface to the import_deps configuration in your .formatter.exs file:

  import_deps: [:ecto, :phoenix, :surface],
  surface_inputs: ["{lib,test}/**/*.{ex,exs,sface}"],

To allow live reloading for .sface files, add the related patterns to the endpoint configuration in your config/dev.exs:

config :my_app, MyAppWeb.Endpoint,
live_reload: [
  patterns: [

If you're using Gettext and you want to use the built-in <ErrorTag> component, you need to configure it in your config.exs so it can properly translate error messages using gettext. If you don't plan to use ErrorTag, you can skip this configuration.

config :surface, :components, [
  {Surface.Components.Form.ErrorTag, default_translator: {MyAppWeb.ErrorHelpers, :translate_error}}

Building components

Surface offers drop-in replacement components for Phoenix.LiveView (Surface.LiveView), Phoenix.Component (Surface.Component), and Phoenix.LiveComponent (Surface.LiveComponent).

Let's build our first Surface.Component and render it in a Surface.LiveView.

Create a file called example_component.ex and place it in your lib/my_app_web/components folder, which should have been previously created by the mix surface.init task. In case you've configured your project manually, you can place it in lib/my_app_web/live.

# lib/my_app_web/components/example_component.ex

defmodule MyAppWeb.Components.ExampleComponent do
  use Surface.Component

  slot default, required: true

  def render(assigns) do

Create a file called example_live.ex and place it in your lib/my_app_web/live folder.

# lib/my_app_web/live/example_live.ex

defmodule MyAppWeb.ExampleLive do
  use Surface.LiveView

  alias MyAppWeb.Components.ExampleComponent

  def render(assigns) do
      Hi there!

Add a new route for our Liveview in your router.ex.

live "/example", ExampleLive

You can now see your Liveview and Component in action at http://localhost:4000/example.

Converting a Phoenix.LiveView to Surface.LiveView

In case you want to migrate an existing Phoenix.Liveview to Surface.Liveview, replace the use Surface.LiveView (or use YourApp, :live_view) with use Surface.Liveview and replace the sigil from ~H/~L with ~F. If you're using colocated files, rename the extension from .heex/.leex to .sface.

Integration with typical Phoenix project's macros (optional)

By default, Surface.LiveView, Surface.Component, and Surface.LiveComponent do not make use of existing macros in YourAppWeb. Using these macros directly in your views will bypass the default YourAppWeb, :live_view/:live_component/:component macros that are generated by default in Phoenix apps (using the --live mix task). Consequently, using the Surface macros directly will not render the default layout nor will make use of any other setup done by the :live_view macro (ex. aliases or imports).

If you want to use the same approach with Surface, you can either:

  • modify the existing YourAppWeb, :live_view macro to use Surface.LiveView in place of Phoenix.LiveView
  • or create a new macro for the explicit use of Surface.LiveView (ex. :surface_view)
defmodule YourAppWeb

  def surface_view do
   quote do
     use Surface.LiveView,
       layout: {YourAppWeb.LayoutView, "live.html"}


defmodule YourAppWeb.ExampleLive
  use YourAppWeb, :surface_view

Modifying the generated :live_view macro will result in wholesale adoption of Surface.LiveView throughout the project, while creating a new macro will require explicit adoption in your project views.

Using Surface with Phoenix templates (optional)

Using Surface components in vanilla Phoenix templates is partially supported but usually not recommended. For more information, please visit Usage with Phoenix templates.

Running the examples (optional)

Most of the components used in the examples presented in this website are just thin wrappers around Bulma components. However, you can easily adapt any of the examples to any library of your preference or try them out with your own CSS styles.

For a quick start with bulma, you can add the following line to your root.html.leex:

<link rel="stylesheet" href=""/>

or add it to the list of dependencies in assets/package.json:

"dependencies": {
  "bulma": "0.8.0"

Surface extends LiveView

The Surface replacement components are wrappers around the respective LiveView components and extend their functionality. A more thorough understanding of how LiveView works and how it is integrated into a Phoenix project can be found at the Phoenix LiveView documentation.

Migrating from v0.5.x to v0.6.x

Surface v0.6.x relies on the Liveview features available since v0.16. The main change from the user perspective is that the stateless Surface.Component now is built on top of Phoenix.Component instead of Phoenix.LiveComponent. This means the mount/1, preload/1 and udpate/2 callbacks are no longer available. If you initialize any assign or compute any value using those callbacks, you need to replace them with one of the new assign helpers.

Migrating from v0.4.x to v0.5.x

Surface v0.5.0 introduces a new syntax which requires migrating components written in previous versions. In order to make the migration process as smooth as possible, Surface v0.5.x ships with a converter that can automatically translate the old syntax into the new one.

Please see the Migration Guide for details.