A basic table component with daisyUI styling.
Supports an optional card/table view toggle for responsive layouts. When items
is provided or toggleable is true, renders both a table view (desktop default)
and a card view (mobile default) with an optional toggle button.
Examples
Basic table (unchanged API)
<.table_default>
<.table_default_header>
<.table_default_row>
<.table_default_header_cell>Name</.table_default_header_cell>
<.table_default_header_cell>Email</.table_default_header_cell>
</.table_default_row>
</.table_default_header>
<.table_default_body>
<.table_default_row>
<.table_default_cell>John Doe</.table_default_cell>
<.table_default_cell>john@example.com</.table_default_cell>
</.table_default_row>
</.table_default_body>
</.table_default>With card/table toggle
<.table_default
id="users-table"
toggleable
items={@users}
card_title={fn user -> user.name end}
card_fields={fn user -> [
%{label: "Email", value: user.email},
%{label: "Role", value: user.role}
] end}
>
<.table_default_header>...</.table_default_header>
<.table_default_body>...</.table_default_body>
<:card_actions :let={user}>
<.button size="sm" navigate={~p"/users/#{user.id}"}>View</.button>
</:card_actions>
<:toolbar_title>
<span class="text-sm text-base-content/60">{length(@users)} users</span>
</:toolbar_title>
<:toolbar_actions>
<.button size="sm" navigate={~p"/users/new"}>Add User</.button>
</:toolbar_actions>
</.table_default>
Summary
Functions
Renders a search input with a magnifying glass icon and debounce.
Renders a sortable table header cell.
Renders a table with daisyUI styling.
Renders a table body section.
Renders a table data cell.
Renders a table footer section.
Renders a table header section.
Renders a table header cell.
Renders a table row.
Functions
Renders a search input with a magnifying glass icon and debounce.
By default emits phx-change="search" with a 300ms debounce. When
on_submit is provided the input is wrapped in a <form> element.
Attributes
value(:string) (required)on_change(:string) - Defaults to"search".on_submit(:string) - Defaults tonil.placeholder(:string) - Defaults tonil.debounce(:integer) - Defaults to300.name(:string) - Defaults to"search".target(:any) - Defaults tonil.class(:string) - Defaults to"".
Renders a sortable table header cell.
When sort is nil, renders an inert <th> label. When sort is a map,
renders a clickable button emitting toggle_sort (or a custom event) with
phx-value-by set to the field key.
States rendered
- Inactive column (sortable, not the current sort) — faint
hero-chevron-up-down-minihint, strengthens on hover viagroup-hover. - Active asc / Active desc — solid chevron-up / chevron-down.
- Loading (during in-flight click) — all chevrons hide and a
loading-spinnershows;pointer-events-noneblocks double-clicks; button dims viaopacity-60. Driven by Phoenix's auto-applied.phx-click-loadingclass — no consumer wiring needed. - Active column with an unrecognised
sort.dir(defensive) — falls back to the inactive up-down hint rather than rendering no icon.
Atom or string sort.dir values are accepted (:asc/:desc/"asc"/"desc").
Attributes
field(:atom) (required)sort(:map) - Current sort: %{by: atom, dir: :asc | :desc}. When nil, renders inert label. Defaults tonil.event(:string) - Defaults to"toggle_sort".target(:any) - Defaults tonil.align(:atom) - Defaults to:left. Must be one of:left,:right, or:center.class(:string) - Defaults to"".- Global attributes are accepted. Supports all globals plus:
["colspan", "rowspan"].
Slots
inner_block(required)
Renders a table with daisyUI styling.
When items is provided or toggleable is true, renders a responsive wrapper
with both table and card views, plus an optional desktop toggle button.
Otherwise renders the classic table-only layout (fully backward compatible).
Attributes
id- Element ID, required when using card/table toggle (optional)class- Additional CSS classes (optional)variant- Table variant: "default", "zebra", "pin-rows", "pin-cols" (optional, default: "default")size- Table size: "xs", "sm", "md", "lg" (optional, default: "md")toggleable- Show card/table toggle buttons on desktop (optional, default: false)items- List of items for card view rendering (optional, default: [])card_title- Function that returns the card title for an item (optional)card_fields- Function that returns a list of%{label: string, value: any}for an item (optional)card_class- Per-card wrapper class. String OR 1-arity function(item) -> string. Default"card card-sm bg-base-200 shadow-sm". Override when the consumer needs a different look (e.g. larger padding, conditional opacity for disabled rows).storage_key- localStorage key for persisting view preference, falls back toidin JS (optional)rest- Additional HTML attributes (optional)
Slots
inner_block- Table content (thead, tbody, etc.)card_body- Fully-custom card content (receives item via:let). When provided, REPLACES the prescribedcard_header+card_title+card_fieldsrendering — the consumer owns the inside of<div class="card-body">.card_actionsfooter still renders if provided. Use for rich cards with badges, icon-prefixed rows, custom layouts.card_media- Media region (image/thumbnail/video preview) rendered ABOVE the card body, receives item via:let. Use for thumbnails, cover images, document previews. The slot owns its own padding/background — wrap content in a styled container.card_actions- Action buttons rendered in each card footer (receives item via :let)toolbar_title- Title/content rendered at the start of the toolbar rowtoolbar_actions- Buttons rendered in the toolbar before the view toggle
Controlled view mode
By default the card/table toggle is driven entirely client-side (JS hook + localStorage).
Pass view_mode="card" or view_mode="table" to take control from the assigns side —
the component then renders ONLY that view (no JS toggle) and the toolbar buttons emit
phx-click={view_event} with phx-value-mode="card"|"table" so the consumer can drive
state via push_patch (URL-backed) or assign. Use this when the view choice must
survive across LV navigation or be part of the URL.
Attributes
id(:string) - Defaults tonil.class(:string) - Defaults to"".variant(:string) - Defaults to"default". Must be one of"default","zebra","pin-rows", or"pin-cols".size(:string) - Defaults to"md". Must be one of"xs","sm","md", or"lg".toggleable(:boolean) - Defaults tofalse.show_toggle(:boolean) - Defaults totrue.items(:list) - Defaults to[].card_title(:any) - Defaults tonil.card_fields(:any) - Defaults tonil.card_class(:any) - Per-card wrapper class. String or 1-arity fn(item) -> string. When fn, called per item. Defaults to"card card-sm bg-base-200 shadow-sm".storage_key(:string) - Defaults tonil.wrapper_class(:string) - Defaults to"rounded-lg shadow-md overflow-x-auto overflow-y-clip".card_grid_class(:string) - Layout classes for the card-view grid (column density, gaps). Must NOT include adisplayutility (grid/hidden) — the component setsdisplayper view-mode branch so controlled table mode can emithiddencleanly. Override to change column count, e.g. a densergap-4 grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5. Must be a literal in a Tailwind-scanned source so the classes are compiled — a dynamically-built string won't be in the generated CSS. Defaults to"gap-4 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4".view_mode(:string) - Controlled view selector. When set, renders ONLY that view and disables the JS toggle; toggle buttons emitview_eventwithphx-value-mode. When nil, falls back to the JS hook + localStorage default. Defaults tonil. Must be one ofnil,"card", or"table".view_event(:string) - Event name emitted by the toggle buttons in controlled mode. Defaults to"switch_view".on_reorder(:string) - When set, the card-view container becomes a SortableGrid hook target. The table-view's tbody is owned by the inner_block — wire that side separately so desktop users get the same DnD as mobile. Defaults tonil.reorder_scope(:map) - Map of scope values exposed on the card-view container as data-sortable-scope-* attrs. Keys are lowercased and dasherized for the DOM attr; the JS hook sends them back to LV as camelCase, so an Elixir key:category_uuidarrives in the LV handler payload as"categoryUuid". Defaults to%{}.reorder_group(:string) - SortableJS group name for cross-container drag (must match the table-view side). Defaults tonil.item_id(:any) - 1-arity function returning the data-id for a card. Defaults to& &1.uuid. Required when on_reorder is set. Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)card_header- Custom header for each card (receives item via :let); replaces card_title.card_body- Fully-custom card body (receives item via :let). When present, replaces card_header + card_title + card_fields rendering.card_media- Media region (image/thumbnail/video) rendered above the card body. Receives item via :let. Owns its own padding/background.card_actions- Action buttons in card footer.above_cards- Content rendered inside the card-view container, above the card grid. Hidden automatically when the JS hook switches to table mode (the wrapper hasmd:hiddentoggled on).sort_bar- Always-visible sort UI rendered in its own row above the table/cards (e.g. a<.sort_selector>). Unlike:above_cards, this slot renders in both views.toolbar_title- Title or arbitrary content rendered at the start of the toolbar row.toolbar_actions- Action buttons rendered in the toolbar, before the view toggle.
Renders a table body section.
Accepts arbitrary HTML attrs via :rest so consumers can wire the
SortableGrid hook directly onto the <tbody>:
<.table_default_body
id="endpoints-list-body"
phx-hook="SortableGrid"
data-sortable="true"
data-sortable-event="reorder_endpoints"
data-sortable-items=".sortable-item"
data-sortable-handle=".pk-drag-handle"
>
...
</.table_default_body>Attributes
- Global attributes are accepted.
Slots
inner_block(required)
Renders a table data cell.
Attributes
class- Additional CSS classes (optional)colspan- Number of columns to span (optional)rowspan- Number of rows to span (optional)rest- Additional HTML attributes (optional)
Attributes
class(:string) - Defaults to"".colspan(:integer) - Defaults tonil.rowspan(:integer) - Defaults tonil.- Global attributes are accepted.
Slots
inner_block(required)
Renders a table header section.
Attributes
class- Overrides the default header styling. Default is"bg-base-300"— a calm, theme-neutral header that reads as a subtle separator from<tbody>instead of the loud daisyUI primary. Pass"bg-primary text-primary-content"to restore the legacy look, orclass=""for a fully bare header. The string fully replaces the default; concatenate manually if you want to add classes on top.
Attributes
class(:string) - Defaults to"bg-base-300".
Slots
inner_block(required)
Renders a table header cell.
:inner_block is optional — a self-closing call (<.table_default_header_cell />)
renders an empty <th>, useful for drag-handle / row-selection columns
that don't need a label.
Attributes
class- Additional CSS classes (optional)rest- Additional HTML attributes (optional)
Attributes
class(:string) - Defaults to"".- Global attributes are accepted.
Slots
inner_block
Renders a table row.
Attributes
class- Additional CSS classes (optional)hover- Enable hover effect: true/false (optional, default: true)rest- Additional HTML attributes (optional)
Attributes
class(:string) - Defaults to"".hover(:boolean) - Defaults totrue.- Global attributes are accepted.
Slots
inner_block(required)