Rich data table with filter bar, server-side sort, and pagination.
All filtering and sorting fires server-side phx events — the developer
queries the database and updates the rows assign. This scales to any
dataset size and integrates cleanly with Ecto.
Use AvenUI.DataTable struct to manage state in your LiveView.
Basic example
<%!-- In the template --%>
<.data_table
id="users-table"
rows={@rows}
table={@table}
on_filter="dt_filter"
on_sort="dt_sort"
on_paginate="dt_paginate"
>
<:filter>
<.dt_search name="search" placeholder="Search users…" />
<.dt_select name="status" prompt="All statuses"
options={[{"Active", "active"}, {"Inactive", "inactive"}]} />
</:filter>
<:col :let={user} label="Name" field="name" sortable>
<div class="flex items-center gap-2">
<.avatar initials={String.first(user.name)} size="sm" />
<span class="font-medium"><%= user.name %></span>
</div>
</:col>
<:col :let={user} label="Email" field="email" sortable>
<%= user.email %>
</:col>
<:col :let={user} label="Status">
<.badge variant={if user.active, do: "success", else: "default"}>
<%= if user.active, do: "Active", else: "Inactive" %>
</.badge>
</:col>
<:col :let={user} label="Joined" field="inserted_at" sortable>
<%= Calendar.strftime(user.inserted_at, "%b %d, %Y") %>
</:col>
<:action :let={user}>
<.button size="xs" variant="ghost" phx-click="edit" phx-value-id={user.id}>
Edit
</.button>
<.button size="xs" variant="ghost" phx-click="delete" phx-value-id={user.id}>
Delete
</.button>
</:action>
</.data_table>Required LiveView handlers
alias AvenUI.DataTable
def mount(_, _, socket) do
{:ok,
socket
|> assign(:table, DataTable.new(per_page: 20))
|> load_rows()}
end
def handle_event("dt_filter", params, socket) do
table = socket.assigns.table
|> DataTable.set_filter(params)
|> DataTable.reset_page()
{:noreply, socket |> assign(:table, table) |> load_rows()}
end
def handle_event("dt_sort", %{"field" => field}, socket) do
table = DataTable.toggle_sort(socket.assigns.table, field)
{:noreply, socket |> assign(:table, table) |> load_rows()}
end
def handle_event("dt_paginate", %{"page" => page}, socket) do
table = DataTable.set_page(socket.assigns.table, String.to_integer(page))
{:noreply, socket |> assign(:table, table) |> load_rows()}
end
defp load_rows(socket) do
dt = socket.assigns.table
{rows, total} = MyApp.Accounts.list_users(
search: DataTable.filter_value(dt, "search"),
status: DataTable.filter_value(dt, "status"),
sort_field: dt.sort_field,
sort_dir: dt.sort_dir,
limit: dt.per_page,
offset: DataTable.offset(dt)
)
socket
|> assign(:rows, rows)
|> assign(:table, DataTable.put_total(dt, total))
end
Summary
Functions
Rich data table with filter bar, sort, and pagination.
Search input for use inside the data_table :filter slot.
Select dropdown for use inside the data_table :filter slot.
Functions
Rich data table with filter bar, sort, and pagination.
Attributes
id(:string) (required)rows(:list) (required)table(:any) (required) - AvenUI.DataTable struct.on_filter(:string) - phx event for filter changes. Defaults to"dt_filter".on_sort(:string) - phx event for sort changes. Defaults to"dt_sort".on_paginate(:string) - phx event for page changes. Defaults to"dt_paginate".title(:string) - Defaults tonil.subtitle(:string) - Defaults tonil.striped(:boolean) - Defaults tofalse.class(:string) - Defaults tonil.
Slots
filter- Filter bar content — use dt_search and dt_select inside.toolbar- Right side of the toolbar — buttons, export, etc.col(required) - Accepts attributes:label(:string)field(:string) - Field name — required for sortable columns.sortable(:boolean)class(:string)align(:string) - Must be one of"left","center", or"right".
action- Action buttons — revealed on row hover.empty- Content shown when rows is empty.
Search input for use inside the data_table :filter slot.
Attributes
name(:string) (required)placeholder(:string) - Defaults to"Search…".value(:string) - Defaults to"".class(:string) - Defaults tonil.
Select dropdown for use inside the data_table :filter slot.
Attributes
name(:string) (required)options(:list) (required) - List of {label, value} tuples.prompt(:string) - Defaults to"All".value(:string) - Defaults to"".class(:string) - Defaults tonil.