Components
Table
Table
Sortable headers, density, stripes - the presentation layer for data. The component renders state and fires events; your app owns the reorder.
The presentation layer for data
Rows in, markup out - and honest sorting: a sortable header is a real button that fires on_sort (default event "sort") with the column's sort_key, aria-sort announces the state, and your app owns the actual reorder. The :footer slot pins a totals row (colspan works); :empty_state renders whenever rows is empty. density="compact", striped, sticky_header and the frameless variant="ghost" restyle it; LiveView streams work unchanged.
| Role | Status | ||
|---|---|---|---|
| Sarah Chen | Engineering | 34 | Active |
| Alex Rivera | Design | 29 | Active |
| Jordan Lee | Support | 41 | Away |
| 3 people | avg 35 | ||
<div class="max-w-full overflow-x-auto">
<.table
sort_by="age"
sort_dir="asc"
rows={[
%{name: "Sarah Chen", role: "Engineering", age: 34, status: "Active"},
%{name: "Alex Rivera", role: "Design", age: 29, status: "Active"},
%{name: "Jordan Lee", role: "Support", age: 41, status: "Away"}
]}
>
<:col :let={p} label="Name" sortable sort_key="name">{p.name}</:col>
<:col :let={p} label="Role">{p.role}</:col>
<:col :let={p} label="Age" sortable sort_key="age">{p.age}</:col>
<:col :let={p} label="Status">
<.badge
color={if p.status == "Active", do: "success", else: "gray"}
variant="soft"
size="sm"
label={p.status}
/>
</:col>
<:footer>
<.td colspan={2}>3 people</.td>
<.td>avg 35</.td>
<.td></.td>
</:footer>
</.table>
</div>
People cells
user_inner_td is the avatar + name + sub-label cell in one helper - avatar_assigns passes straight through to the avatar, so name-hashed initials work with no photos anywhere.
| User | Plan |
|---|---|
|
AR
Alex Rivera
alex@example.com
| Team |
|
AL
Ada Lovelace
ada@analytical.engine
| Pro |
<div class="max-w-full overflow-x-auto">
<.table rows={[
%{name: "Alex Rivera", email: "alex@example.com", plan: "Team"},
%{name: "Ada Lovelace", email: "ada@analytical.engine", plan: "Pro"}
]}>
<:col :let={u} label="User">
<.user_inner_td
label={u.name}
sub_label={u.email}
avatar_assigns={%{name: u.name, size: "sm"}}
/>
</:col>
<:col :let={u} label="Plan">{u.plan}</:col>
</.table>
</div>
Properties
<.table>
| Attribute | Type | Default | Description |
|---|---|---|---|
class
|
any |
nil
|
CSS class |
density
|
string |
"comfortable"
|
row height. compact halves the vertical padding for data-heavy screens
one of: "comfortable", "compact"
|
id
|
string |
||
on_sort
|
any |
"sort"
|
event name (or JS command) fired when a sortable header is clicked; receives phx-value-sort of the column's sort_key. May also be a 1-arity function of the sort_key returning the event/JS per column - how the data table patches per-column sort URLs in link mode. |
rest
|
global |
||
row_click
|
any |
nil
|
the function for handling phx-click on each row |
row_id
|
any |
nil
|
the function for generating the row id |
row_item
|
any |
&Function.identity/1
|
the function for mapping each row before calling the :col slot |
rows
|
list |
[]
|
the list of rows to render |
sort_by
|
any |
nil
|
the currently sorted column's sort_key |
sort_dir
|
string |
"asc"
|
the current sort direction
one of: "asc", "desc"
|
sticky_header
|
boolean |
false
|
keep the header row pinned while the table scrolls (the table needs a scrollable ancestor) |
striped
|
boolean |
false
|
wash alternate rows for easier scanning |
variant
|
string |
"basic"
|
one of: "ghost", "basic"
|
:col
slot
|
slot |
||
:empty_state
slot
|
slot |
A message to show when the table is empty, to be used together with :col | |
:footer
slot
|
slot |
a totals/summary row pinned under the body - supply <.td> cells (colspan works), e.g. <.td colspan={3}>Total</.td><.td>$2,500</.td> |
<.user_inner_td>
| Attribute | Type | Default | Description |
|---|---|---|---|
avatar_assigns
|
map |
nil
|
if using an avatar, this map will be passed to the avatar component as props |
class
|
any |
nil
|
CSS class |
label
|
string |
nil
|
Adds a label your user, e.g name |
rest
|
global |
||
sub_label
|
string |
nil
|
Adds a sub-label your to your user, e.g title |