Data-driven list component.
A type: :list node in your render tree is shorthand for a scrollable list
backed by Elixir data. Mob expands it into a lazy_list before rendering,
wrapping each row in a tappable container.
Basic usage
The default renderer turns each item into a text row. No setup needed:
%{
type: :list,
props: %{id: :my_list, items: assigns.names},
children: []
}Handle selections in handle_info/2:
def handle_info({:select, :my_list, index}, socket) do
item = Enum.at(socket.assigns.names, index)
{:noreply, Mob.Socket.assign(socket, :selected, item)}
endCustom renderer
Register a renderer in mount/3 to control how each item looks:
def mount(_params, _session, socket) do
socket =
socket
|> Mob.Socket.assign(:items, load_items())
|> Mob.List.put_renderer(:my_list, fn %{name: name, subtitle: sub} ->
%{
type: :column,
props: %{padding: 12},
children: [
%{type: :text, props: %{text: name, text_size: :base}, children: []},
%{type: :text, props: %{text: sub, text_size: :sm, text_color: :gray_500}, children: []}
]
}
end)
{:ok, socket}
endDefault renderer rules
When no custom renderer is registered:
- Binary → text row with the string
%{label: _}→ text row with the label%{text: _}→ text row with the text- Anything else →
inspect/1fallback
Summary
Functions
The default item renderer. Handles binaries, maps with :label/:text, and
falls back to inspect/1 for anything else.
Walk a render tree and expand all type: :list nodes into lazy_list nodes.
Called internally by Mob.Screen before passing the tree to Mob.Renderer.
renderers is the list_renderers map from socket.__mob__. pid is the
screen process (used as the tap target for row-select events).
@spec put_renderer(Mob.Socket.t(), atom(), (term() -> map())) :: Mob.Socket.t()
Register a custom item renderer for a list.
id must match the :id prop on the type: :list node.
renderer is a 1-arity function that receives one item and returns a node map.
Call this from mount/3 or handle_info/2 — it is stored in socket.__mob__
and picked up at render time.