Skip to main content
This guide will walk you through creating a simple interactive thermostat using Phoenix LiveView. You’ll learn the basics of LiveView by building a working application that responds to user input in real-time.
This guide assumes you already have a Phoenix application set up. If you don’t, check out the Phoenix installation guide first. LiveView is included by default in Phoenix 1.6+.

Build a thermostat

We’ll create a simple thermostat that displays the current temperature and allows users to increase or decrease it.
1

Create the LiveView module

Create a new file at lib/my_app_web/live/thermostat_live.ex (replace my_app_web with your app’s name):
defmodule MyAppWeb.ThermostatLive do
  use MyAppWeb, :live_view

  def render(assigns) do
    ~H"""
    <div class="thermostat">
      <h1>Thermostat</h1>
      <p>Current temperature: {@temperature}°F</p>
      <button phx-click="inc_temperature">+</button>
    </div>
    """
  end

  def mount(_params, _session, socket) do
    temperature = 70 # Starting temperature
    {:ok, assign(socket, :temperature, temperature)}
  end

  def handle_event("inc_temperature", _params, socket) do
    {:noreply, update(socket, :temperature, &(&1 + 1))}
  end
end
This LiveView defines three essential callbacks:
  • render/1 receives the socket assigns and returns the HTML to display using the ~H sigil (HEEx template)
  • mount/3 initializes the LiveView state when it first loads
  • handle_event/3 handles the button click event
2

Add the route

Open your router file at lib/my_app_web/router.ex and add a live route:
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :browser do
    # ... existing pipeline config
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    # Add this line:
    live "/thermostat", ThermostatLive
  end
end
The live macro creates a route that serves your LiveView directly.
3

Start the server and test

Start your Phoenix server:
mix phx.server
Visit http://localhost:4000/thermostat in your browser. You should see the thermostat with the current temperature and a + button. Click the button and watch the temperature increase in real-time!
4

Add a decrease button

Let’s make the thermostat more useful by adding a decrease button. Update the render/1 function:
def render(assigns) do
  ~H"""
  <div class="thermostat">
    <h1>Thermostat</h1>
    <p>Current temperature: {@temperature}°F</p>
    <div class="controls">
      <button phx-click="dec_temperature">-</button>
      <button phx-click="inc_temperature">+</button>
    </div>
  </div>
  """
end
Add the event handler:
def handle_event("dec_temperature", _params, socket) do
  {:noreply, update(socket, :temperature, &(&1 - 1))}
end
Refresh your browser and try both buttons. The temperature updates instantly!

How it works

The LiveView lifecycle

  1. Initial HTTP request: When you first visit /thermostat, Phoenix renders the LiveView as static HTML through a regular HTTP request. This ensures fast “First Meaningful Paint” and helps with SEO.
  2. WebSocket connection: After the page loads, the JavaScript client (automatically included in Phoenix apps) establishes a WebSocket connection to the server.
  3. LiveView process: A new LiveView process is spawned on the server. The mount/3 callback runs again to initialize the state.
  4. User interaction: When you click a button, the client sends an event to the server over WebSocket. The handle_event/3 callback processes it and updates the state.
  5. Minimal updates: LiveView calculates exactly what changed and sends only the diff to the client. The client efficiently patches the DOM.

Understanding the code

The ~H sigil: This is HEEx (HTML+EEx), LiveView’s templating language. It provides:
  • HTML validation at compile time
  • Automatic XSS protection
  • Support for function components
  • Interpolation with {@variable} syntax
The phx-click binding: This tells LiveView to send an event to the server when the element is clicked. LiveView supports many bindings: phx-submit, phx-change, phx-focus, phx-blur, and more. The socket: The socket holds all the state for your LiveView in its assigns. Functions like assign/3 and update/3 return a new socket with updated assigns. Because Elixir data is immutable, LiveView can efficiently track what changed.

Add temperature limits

Let’s make our thermostat more realistic by adding temperature limits and visual feedback:
def render(assigns) do
  ~H"""
  <div class="thermostat">
    <h1>Thermostat</h1>
    <p class={temp_class(@temperature)}>
      Current temperature: {@temperature}°F
    </p>
    <div class="controls">
      <button phx-click="dec_temperature" disabled={@temperature <= 60}>-</button>
      <button phx-click="inc_temperature" disabled={@temperature >= 80}>+</button>
    </div>
  </div>
  """
end

def handle_event("inc_temperature", _params, socket) do
  new_temp = min(socket.assigns.temperature + 1, 80)
  {:noreply, assign(socket, :temperature, new_temp)}
end

def handle_event("dec_temperature", _params, socket) do
  new_temp = max(socket.assigns.temperature - 1, 60)
  {:noreply, assign(socket, :temperature, new_temp)}
end

defp temp_class(temp) when temp >= 75, do: "hot"
defp temp_class(temp) when temp <= 65, do: "cold"
defp temp_class(_temp), do: "comfortable"
Now the thermostat won’t go below 60°F or above 80°F, and the temperature display will have different CSS classes based on the temperature.

Next steps

Assigns and HEEx

Learn more about templates and data binding

Bindings

Explore all available LiveView bindings

Forms

Build forms with live validation

Components

Create reusable function components
Want to see more examples? Run mix phx.gen.live to generate a complete LiveView CRUD interface with database integration.

Build docs developers (and LLMs) love