Properties

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

Where:

  • name - is the name of the property.
  • type - an atom defining the type of the property. See all available types in section "Components Basics > Types available".
  • 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 or range of values suggested for the property.
  • values! - the list or range of all possible values for the property. Unlike values, this option enforces validation of the default value against the given list.
  • from_context - if the prop is not passed or the value is nil, sets the value to the one stored in the context under the given key.
  • 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

  def render(assigns) do
    ~F"""
    Hello, {@name}!
    """
  end
end

# Using the component

defmodule Example do
  use Surface.Component

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

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

  slot default

  def render(assigns) do
    ~F"""
    <button class={"button", "is-info", "is-loading": @loading, "is-rounded": @rounded}>
      <#slot />
    </button>
    """
  end
end

Let's try it out.

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

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.

Event property

In order to declare an event property, you must use the prop macro and define the type as :event:

prop name, :event, options

Where:

  • name - is the name of the event.
  • options - a keyword list of options for additional customization.

Supported options

  • required - declares the event as required. Default is false.
  • default - defines a default value for an optional event.

Example:

defmodule MyButton do
  use Surface.Component

  @doc "Triggers on click"
  prop click, :event

  slot default

  def render(assigns) do
    ~F"""
    <button class="button" :on-click={@click}>
      <#slot />
    </button>
    """
  end
end