A styling and sorting recipe layered over Shadix.Components.Table.
The shadcn "Data Table" example is not a standalone component: it is a guide
for wiring TanStack Table (column defs, sorting,
filtering, pagination, row selection, visibility) into the presentational
Table primitives. This v1 is deliberately not a TanStack port. It is a
thin set of helpers that render the chrome — a bordered container, sortable
column-header buttons, and an optional pagination bar — on top of the existing
Table components.
Scope and simplifications
- The consumer owns the data and all state. Your LiveView holds the rows, the current sort field/direction, and the current page; it does the actual sorting and slicing. These helpers only render the surface. There is no LiveView hook and no client-side state.
- You build the table yourself.
data_table/1is just a bordered wrapper: put a<Shadix.Components.Table.table>(with its own header/body) inside it. The recipe does not generate rows from column definitions. - Sorting is "controlled".
data_table_column_header/1renders a button that shows the current sort direction for that column (via:sort, one of"asc","desc", ornil) and runs the:on_sortJS you pass. Your handler decides the next state and re-renders with an updated:sort. - Pagination is "controlled".
data_table_pagination/1renders prev/next buttons and a "Page X of Y" label. It enforces nothing; your:on_prev/:on_nexthandlers update the page and you pass:page/:total_pagesback in. Buttons can be disabled via:prev_disabled/:next_disabled.
data-slots
data-table— the bordered container<div>.data-table-column-header— the sortable header<button>.data-table-pagination— the pagination bar<div>(a labellednavigationlandmark).
Accessibility
The sortable header is a plain <button> and intentionally does not carry
aria-sort — that attribute is not allowed on <button>; it belongs on the
columnheader (the <th>). If you want to expose the sort state to assistive
technology via aria-sort, set it on the consuming
<Shadix.Components.Table.table_head> (e.g. aria-sort="ascending"). The
button instead conveys the sort control through an aria-label.
Example
<Shadix.Components.DataTable.data_table>
<Shadix.Components.Table.table>
<Shadix.Components.Table.table_header>
<Shadix.Components.Table.table_row>
<Shadix.Components.Table.table_head>
<Shadix.Components.DataTable.data_table_column_header
label="Name"
sort={@sort_dir_for_name}
on_sort={JS.push("sort", value: %{field: "name"})}
/>
</Shadix.Components.Table.table_head>
</Shadix.Components.Table.table_row>
</Shadix.Components.Table.table_header>
<Shadix.Components.Table.table_body>
<Shadix.Components.Table.table_row :for={row <- @rows}>
<Shadix.Components.Table.table_cell>{row.name}</Shadix.Components.Table.table_cell>
</Shadix.Components.Table.table_row>
</Shadix.Components.Table.table_body>
</Shadix.Components.Table.table>
</Shadix.Components.DataTable.data_table>
<Shadix.Components.DataTable.data_table_pagination
page={@page}
total_pages={@total_pages}
prev_disabled={@page <= 1}
next_disabled={@page >= @total_pages}
on_prev={JS.push("prev_page")}
on_next={JS.push("next_page")}
/>
Summary
Functions
A bordered container for a data table.
A sortable column header button for use inside a table_head.
An optional pagination bar with prev/next buttons and a "Page X of Y" label.
Functions
A bordered container for a data table.
Renders a rounded-md border wrapper carrying data-slot="data-table". Place a
<Shadix.Components.Table.table> inside via the inner block; the table's own
scroll container nests cleanly within the rounded border.
Attributes
class(:string) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
A sortable column header button for use inside a table_head.
Renders a <button data-slot="data-table-column-header"> showing :label and a
chevron reflecting :sort:
"asc"— up chevron"desc"— down chevronnil(default) — neutral up/down chevrons
Clicking runs the caller's :on_sort JS. The component is "controlled": it
renders the sort state you give it and does not toggle anything itself — your
handler computes the next direction and passes back an updated :sort.
The button carries an aria-label describing the sort control (and the current
direction). aria-sort is not placed on the button — it is not a valid
attribute there; set it on the consuming <th>/table_head instead.
Attributes
label(:string) (required)sort(:string) - Current sort direction for this column: "asc", "desc", or nil. Defaults tonil. Must be one of"asc","desc", ornil.on_sort(Phoenix.LiveView.JS) - JS run when the header is clicked. Defaults to%Phoenix.LiveView.JS{ops: []}.class(:string) - Defaults tonil.- Global attributes are accepted.
An optional pagination bar with prev/next buttons and a "Page X of Y" label.
Purely presentational and "controlled": it renders :page / :total_pages and
runs :on_prev / :on_next. Disable the edges with :prev_disabled /
:next_disabled; your handlers update the page state you pass back in.
Attributes
page(:integer) - Defaults to1.total_pages(:integer) - Defaults to1.prev_disabled(:boolean) - Defaults tofalse.next_disabled(:boolean) - Defaults tofalse.on_prev(Phoenix.LiveView.JS) - JS run when the previous-page button is clicked. Defaults to%Phoenix.LiveView.JS{ops: []}.on_next(Phoenix.LiveView.JS) - JS run when the next-page button is clicked. Defaults to%Phoenix.LiveView.JS{ops: []}.class(:string) - Defaults tonil.- Global attributes are accepted.