Stateful components like Surface.LiveView and Surface.LiveComponent hold their state in the socket's assigns. In Surface, those assigns are called data assigns.

In order to declare a data assign, you must use the data macro:

data name, type, options


  • name - is the name of the assign.
  • type - an atom defining the type of the assign. See all available types in section "Components Basics > Types available".
  • options - a keyword list of options for additional customization.

Supported options

  • default - defines a default value for the assign.
  • values - the list or range of values suggested for the assign.
  • values! - the list or range of all possible values for the assign. Unlike values, this option enforces validation of the default value against the given list.
  • from_context - sets the value to the one stored in the context under the given key.
  • css_variant - Tailwind only. If set to true, instructs the Surface compiler to generate CSS variants based on the related assign's type and value. Default is false. For further information, see "Generating CSS variants ".

In the following example, we create a simple Counter component that has its state defined by a single assign named count. You can use the - and + buttons to decrement/increment the counter's value.

Note: The data macro is available in both stateful and stateless components. It allows you to define intermediary assigns in the update function of the component that computes values based on one or several properties.


defmodule Counter do
  use Surface.LiveComponent

  data count, :integer, default: 0

  def render(assigns) do
      <h1 class="title">
        <button class="button is-info" :on-click="dec">
        <button class="button is-info" :on-click="inc">

  def handle_event("inc", _value, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}

  def handle_event("dec", _value, socket) do
    {:noreply, update(socket, :count, &(&1 - 1))}

Note: Since we defined a default value for count, we don't need to implement the mount/1 callback to initialize it. All data assigns with default values will be automatically initialized by Surface. That means if you need some data to be initialized, you need to add a default value, even if it's nil. If you do not add a default value, the data is not initialized.