You can pass information from a parent component down to a child using properties.

In order to declare a property, you must use the prop macro:

prop name, type, options


  • name - is the name of the property.
  • type - an atom defining the type of the property. See all available types in section "Property types".
  • options - a keyword list of options for additional customization.

Supported options

  • required - declares the property as required. Default is false.
  • default - defines a default value for an optional property.
  • values - the list of possible values for the property.
  • accumulate - instructs Surface to group all different values provided for that property into a single list. Default is false, i.e. only the last value is passed.
Hello, John Doe!

# Defining the component

defmodule Hello do
use Surface.Component

@doc "Someone to say hello to"
prop name, :string, required: true, default: ""

def render(assigns) do
Hello, {{ @name }}!

# Using the component

defmodule Example do
use Surface.Component

def render(assigns) do
<Hello name="John Doe"/>

Property types

When declaring a property, you can define the type of the assign using one of the following types:

:any, :css_class, :list, :event, :boolean, :string, :date, :datetime, :number, :integer, :decimal, :map, :fun, :atom, :module, :changeset, :form and :keyword.

Note: Currently, some of the types above work just as annotations and don't have any practical use aside from documentation. If the type you need is not in that list, you can safely use :any instead. However, some other types like :css_class, :list and :event are handled differently, i.e. there are extra rules and behaviours applied to them.

CSS class property

In order to avoid working with string concatenation, which is annoying and error-prone, Surface allows passing keyword lists directly to the class property and improves developer experience by automatically handling conditional classes. Let's see how it works.

Imagine you want to create a button component that sets CSS classes based on the following rules:

  • button - always set
  • is-info - always set
  • is-loading - set if @loading is truthy
  • is-rounded - set if @rounded is truthy

We can define our component like this:

defmodule MyButton do
use Surface.Component

prop loading, :boolean
prop rounded, :boolean

def render(assigns) do
<button class={{ "button", "is-info", "is-loading": @loading, "is-rounded": @rounded }}>

Let's try it out.

<MyButton loading={{ @loading }} rounded={{ @rounded }}>
Change my style!

Note: For regular HTML tags like <button>, the class attribute will be handled automatically as expected. For custom components, you need to instruct Surface to do so by setting the type of the property as :css_class.