PhoenixKitWeb.Components.Core.TableDefault (phoenix_kit v2.15.1)

Copy Markdown View Source

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

Drag-handle cell for table-view DnD reorder.

Empty <th> sized to match the drag-handle column. Sits in the header row alongside <.bulk_select_header_cell> and the regular <.table_default_header_cell>s.

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

drag_handle_cell(assigns)

Drag-handle cell for table-view DnD reorder.

Renders a <td> containing the hero-bars-3 grip icon, with the classes the SortableGrid hook needs to find it (.pk-drag-handle). The icon is always visible — a muted tone that strengthens on hover. It used to hide until row hover (opacity-0 group-hover/row:opacity-100), but an affordance you cannot see is one nobody discovers: users had no idea rows were draggable until they happened to mouse over the right cell. Deliberate product call (2026-08-14) — do not quietly restore the hover reveal.

Pair this with a <tbody phx-hook="SortableGrid"> setup (see the <.table_default> moduledoc / phoenix_kit_projects reference call sites). The cell only renders the affordance — the hook owns the drag mechanics.

Attributes

  • class (:any) - Defaults to "w-8".
  • title (:string) - Defaults to nil.
  • Global attributes are accepted.

drag_handle_header_cell(assigns)

Empty <th> sized to match the drag-handle column. Sits in the header row alongside <.bulk_select_header_cell> and the regular <.table_default_header_cell>s.

Separate component (rather than a bare <.table_default_header_cell class="w-8" />) so the intent is obvious at the call site, and the width default matches <.drag_handle_cell>'s without consumers having to keep them in sync.

Attributes

  • class (:any) - Defaults to "w-8".

search_toolbar(assigns)

Renders a search input with a magnifying glass icon and debounce.

Emits phx-change="search" (override via on_change) with a 300ms debounce. The input is ALWAYS wrapped in a <form> — LiveView's client refuses phx-change on an input outside a form (it throws in the browser console and the event never reaches the server, which reads as "search silently does nothing"; the component's original no-on_submit branch rendered exactly that trap and its first real caller hit it). Pressing Enter fires on_submit when given, otherwise the same on_change event — so Enter always means "search now".

Attributes

  • value (:string) (required)
  • on_change (:string) - Defaults to "search".
  • on_submit (:string) - Defaults to nil.
  • placeholder (:string) - Defaults to nil.
  • debounce (:integer) - Defaults to 300.
  • name (:string) - Defaults to "search".
  • target (:any) - Defaults to nil.
  • id (:string) - Id for the wrapping <form>. Without one, LiveView form recovery is silently disabled and host suites warn missing_form_id. Defaults to a value derived from the change event, which is unique per page in practice; pass an explicit id when two search toolbars share a page. Defaults to nil.
  • loading_indicator (:boolean) - Show a spinner while the change event is in flight (LiveView's phx-change-loading on the input). Pass false when results appear instantly anyway — e.g. a client-side filter (TableLocalSearch) — where a spinner would be noise. Defaults to true.
  • class (:any) - Defaults to "".

sort_header_cell(assigns)

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-mini hint, strengthens on hover via group-hover.
  • Active asc / Active desc — solid chevron-up / chevron-down.
  • Loading (during in-flight click) — all chevrons hide and a loading-spinner shows; pointer-events-none blocks double-clicks; button dims via opacity-60. Driven by Phoenix's auto-applied .phx-click-loading class — 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 to nil.

  • event (:string) - Defaults to "toggle_sort".
  • target (:any) - Defaults to nil.
  • align (:atom) - Defaults to :left. Must be one of :left, :right, or :center.
  • class (:any) - Defaults to "".
  • Global attributes are accepted. Supports all globals plus: ["colspan", "rowspan"].

Slots

  • inner_block (required)

table_default(assigns)

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 to id in 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 prescribed card_header + card_title + card_fields rendering — the consumer owns the inside of <div class="card-body">. card_actions footer 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 row
  • toolbar_actions - Buttons rendered in the toolbar before the view toggle

Controlled view mode

By default the card / comfortable / compact toggle is driven entirely client-side (JS hook + localStorage; comfortable is the default when nothing is stored). Pass view_mode="card", view_mode="comfy", 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"|"comfy"|"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 to nil.

  • class (:any) - 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 to false.

  • show_toggle (:boolean) - Defaults to true.

  • items (:list) - Defaults to [].

  • card_title (:any) - Defaults to nil.

  • card_fields (:any) - Defaults to nil.

  • 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 to nil.

  • 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 a display utility (grid/hidden) — the component sets display per view-mode branch so controlled table mode can emit hidden cleanly. Override to change column count, e.g. a denser gap-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 emit view_event with phx-value-mode. When nil, falls back to the JS hook + localStorage default. Defaults to nil. Must be one of nil, "card", "table", or "comfy".

  • 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 to nil.

  • 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_uuid arrives 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 to nil.

  • item_id (:any) - 1-arity function returning the data-id for a card. Defaults to & &1.uuid. Required when on_reorder is set. Defaults to nil.

  • card_media_class (:string) - Classes for the :card_media wrapper.

    Defaults to no class at all, which is exactly what the wrapper was before this attr existed — an existing consumer renders unchanged. Pass "relative" for a positioning context if the slot puts a corner overlay (a checkbox, a drag handle, a spinner) over the media.

    For a card GRID, pass a fixed band instead — "relative h-24 bg-base-200 overflow-hidden" is the shape the catalogue module uses everywhere: a card with a photo and one without are then the same height and the grid stays even. Cards drift apart when each page invents its own, so pick one per module and pass it consistently.

    Defaults to nil.

  • 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. The slot owns its own padding/background; pass card_media_class to frame the wrapper so cards look alike across pages.
  • 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 has md:hidden toggled 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.

table_default_body(assigns)

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)

table_default_cell(assigns)

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 (:any) - Defaults to "".
  • colspan (:integer) - Defaults to nil.
  • rowspan (:integer) - Defaults to nil.
  • Global attributes are accepted.

Slots

  • inner_block (required)

table_default_footer(assigns)

Renders a table footer section.

Slots

  • inner_block (required)

table_default_header(assigns)

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, or class="" for a fully bare header. The string fully replaces the default; concatenate manually if you want to add classes on top.

Attributes

  • class (:any) - Defaults to "bg-base-300".

Slots

  • inner_block (required)

table_default_header_cell(assigns)

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 (:any) - Defaults to "".
  • Global attributes are accepted.

Slots

  • inner_block

table_default_row(assigns)

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 (:any) - Defaults to "".
  • hover (:boolean) - Defaults to true.
  • Global attributes are accepted.

Slots

  • inner_block (required)