Table

Renders a simple table.

Read more Read less

Maturity: Developing

Usage

<.table id="pets" rows={@pets} caption="Pets">
  <:col :let={p} label="name"><%= p.name %></:col>
  <:col :let={p} label="age"><%= p.age %></:col>
</.table>

Row actions

row_click sets phx-click on each cell, which reaches pointer users only. Where you use it, repeat the action as a link or button inside the row, so that it can also be reached by keyboard.

<.table
  id="pets"
  rows={@pets}
  caption="Pets"
  row_click={&JS.navigate(~p"/pets/#{&1}")}
>
  <:col :let={p} label="name">
    <.link navigate={~p"/pets/#{p}"}><%= p.name %></.link>
  </:col>
</.table>

Scroll container

The div around the table has tabindex="0", so that a table that is wider than its container can be scrolled using the keyboard. This assumes that you make the container scrollable:

.table-container {
  overflow-x: auto;
}

Set caption or label to name the container, which is then exposed as a region. A blank value counts as unset.

If the table fits its container, or sits in another scroll container, set scrollable={false}. The wrapper then has no tabindex, no role and no name, and does not add a useless tab stop.

NameAge
George 8
Mary 5
<div style="inline-size: 100%">
  <.table
    id="table-single-basic"
    rows={[%{id: 1, name: "George", age: 8}, %{id: 2, name: "Mary", age: 5}]}
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
  </.table>
</div>
NameAge
George 8
Mary 5
<div style="inline-size: 100%">
  <.table
    id="table-single-column-attributes"
    rows={[%{id: 1, name: "George", age: 8}, %{id: 2, name: "Mary", age: 5}]}
  >
    <:col :let={p} label="Name" col_attrs={[style: "width: 70%"]}><%= p.name %></:col>
    <:col :let={p} label="Age" col_attrs={[style: "width: 30%"]}><%= p.age %></:col>
  </.table>
</div>
NameAge
George 8
Mary 5
<div style="inline-size: 100%">
  <.table
    id="table-single-row-id-and-item"
    rows={[{1, %{name: "George", age: 8}}, {2, %{name: "Mary", age: 5}}]}
    row_id={fn {id, _} -> "pet-#{id}" end}
    row_item={fn {_, pet} -> pet end}
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
  </.table>
</div>
NameAge Actions
George 8 Edit
Mary 5 Edit
<div style="inline-size: 100%">
  <.table
    id="table-single-actions"
    rows={[%{id: 1, name: "George", age: 8}, %{id: 2, name: "Mary", age: 5}]}
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
    <:action label="Actions">
      <.link>Edit</.link>
    </:action>
  </.table>
</div>
List of pets filtered by species
NameAge
George 8
Mary 5
<div style="inline-size: 100%">
  <.table
    id="table-single-caption"
    rows={[%{id: 1, name: "George", age: 8}, %{id: 2, name: "Mary", age: 5}]}
    caption="List of pets filtered by species"
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
  </.table>
</div>

row_click sets phx-click on each cell, which only pointer users can reach. Repeat the action inside the row, as the button in the last column does here, so that it is also reachable by keyboard.

NameAge Actions
George 8
Mary 5
<div style="inline-size: 100%">
  <.table
    id="table-single-row-click"
    rows={[%{id: 1, name: "George", age: 8}, %{id: 2, name: "Mary", age: 5}]}
    row_click={&JS.toggle(to: "#table-row-clicked-#{&1.id}")}
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
    <:action :let={p} label="Actions">
      <.button type="button" phx-click={JS.toggle(to: "#table-row-clicked-#{p.id}")}>Pick</.button>
    </:action>
  </.table>
  <p id="table-row-clicked-1" hidden>You picked George.</p>
  <p id="table-row-clicked-2" hidden>You picked Mary.</p>
</div>

The wrapper is in the tab order so that the table can also be scrolled by keyboard. Set label to give it an accessible name, or set caption to use the name as both region name and table caption.

NameBreedAgeSexWeightColourMicrochipVaccinatedNeuteredShelterArrivedOwnerEmailPhoneLicenceStatus
George Dachshund 8 Male 9.2 kg Black and tan 981020012345678 2026-03-14 Yes Bergedorf Animal Shelter 2025-11-02 Katrin Dinkelschrot katrin.dinkelschrot@example.com +49 40 123456 HH-2025-004871 Available
Mary Golden Retriever 5 Female 28.6 kg Cream 981020087654321 2026-01-27 No Altona Rescue Centre 2026-02-19 Hendrik Vollmerhausen hendrik.vollmerhausen@example.com +49 40 654321 HH-2026-001294 Reserved
<div style="inline-size: 100%">
  <.table
    id="table-single-scrolling"
    label="Adoptable pets"
    rows={[%{id: 1, name: "George", owner: "Katrin Dinkelschrot", status: "Available", color: "Black and tan", email: "katrin.dinkelschrot@example.com", weight: "9.2 kg", age: 8, arrived: "2025-11-02", breed: "Dachshund", chip: "981020012345678", licence: "HH-2025-004871", neutered: "Yes", phone: "+49 40 123456", sex: "Male", shelter: "Bergedorf Animal Shelter", vaccinated: "2026-03-14"}, %{id: 2, name: "Mary", owner: "Hendrik Vollmerhausen", status: "Reserved", color: "Cream", email: "hendrik.vollmerhausen@example.com", weight: "28.6 kg", age: 5, arrived: "2026-02-19", breed: "Golden Retriever", chip: "981020087654321", licence: "HH-2026-001294", neutered: "No", phone: "+49 40 654321", sex: "Female", shelter: "Altona Rescue Centre", vaccinated: "2026-01-27"}]}
  >
    <:col :let={p} label="Name"><%= p.name %></:col>
    <:col :let={p} label="Breed"><%= p.breed %></:col>
    <:col :let={p} label="Age"><%= p.age %></:col>
    <:col :let={p} label="Sex"><%= p.sex %></:col>
    <:col :let={p} label="Weight"><%= p.weight %></:col>
    <:col :let={p} label="Colour"><%= p.color %></:col>
    <:col :let={p} label="Microchip"><%= p.chip %></:col>
    <:col :let={p} label="Vaccinated"><%= p.vaccinated %></:col>
    <:col :let={p} label="Neutered"><%= p.neutered %></:col>
    <:col :let={p} label="Shelter"><%= p.shelter %></:col>
    <:col :let={p} label="Arrived"><%= p.arrived %></:col>
    <:col :let={p} label="Owner"><%= p.owner %></:col>
    <:col :let={p} label="Email"><%= p.email %></:col>
    <:col :let={p} label="Phone"><%= p.phone %></:col>
    <:col :let={p} label="Licence"><%= p.licence %></:col>
    <:col :let={p} label="Status"><%= p.status %></:col>
  </.table>
</div>