Tables with Hotwire

Interactive table components with Turbo Frames & Stimulus

← Back to Home

Sortable Table

Click column headers to sort • Powered by Stimulus

Name
Role
Email
Status
John Doe Admin [email protected] Active
Jane Smith Editor [email protected] Inactive
Mike Johnson Viewer [email protected] Pending
Sarah Williams Manager [email protected] Active
David Brown Admin [email protected] Inactive
HTML + Stimulus
<div data-controller="table">
  <table class="min-w-full divide-y divide-gray-200">
    <thead class="bg-gray-50">
      <tr>
        <th 
          class="px-6 py-3 cursor-pointer"
          data-action="click->table#sort"
          data-column="name"
        >
          <div class="flex items-center space-x-1">
            <span>Name</span>
            <svg data-table-target="sortIcon">...</svg>
          </div>
        </th>
      </tr>
    </thead>
    <tbody>
      <tr data-table-target="row">
        <td data-sort-value="John Doe">John Doe</td>
      </tr>
    </tbody>
  </table>
</div>

Searchable Table with Live Filter

Client-side instant search • Powered by Stimulus

Name Email Role Status
John Doe [email protected] Admin Active
Jane Smith [email protected] Editor Inactive
Mike Johnson [email protected] Viewer Pending
Sarah Williams [email protected] Manager Active
David Brown [email protected] Admin Inactive
Emily Davis [email protected] Editor Pending
Chris Wilson [email protected] Viewer Active
Lisa Anderson [email protected] Manager Inactive
HTML + Stimulus
<div data-controller="table">
  <input 
    type="text" 
    placeholder="Search..."
    data-action="input->table#search"
  >
  
  <table>
    <tbody>
      <tr data-table-target="row">
        <td>John Doe</td>
        <td>[email protected]</td>
      </tr>
    </tbody>
  </table>
  
  <div data-empty-state class="hidden">
    No results found
  </div>
</div>

Bulk Actions & Row Selection

Select rows • Perform bulk operations • Powered by Stimulus

Name Email Role Status
JD
John Doe
[email protected] Admin Active
JS
Jane Smith
[email protected] Editor Inactive
MJ
Mike Johnson
[email protected] Viewer Pending
SW
Sarah Williams
[email protected] Manager Active
DB
David Brown
[email protected] Admin Inactive
HTML + Stimulus
<div data-controller="table">
  <!-- Bulk Actions Bar -->
  <div data-table-target="bulkActions" class="hidden">
    <span data-count>0</span> items selected
    <button data-action="click->table#bulkExport">Export</button>
    <button data-action="click->table#bulkDelete">Delete</button>
  </div>

  <table>
    <thead>
      <tr>
        <th>
          <input 
            type="checkbox"
            data-table-target="checkbox selectAll"
            data-action="change->table#toggleAll"
          >
        </th>
        <th>Name</th>
      </tr>
    </thead>
    <tbody>
      <tr data-id="1">
        <td>
          <input 
            type="checkbox"
            data-table-target="checkbox"
            data-action="change->table#toggleRow"
          >
        </td>
        <td>John Doe</td>
      </tr>
    </tbody>
  </table>
</div>

Expandable Rows

Click to expand & show additional details • Powered by Stimulus

Product Price Stock Status
Wireless Headphones $299.99 145 In Stock
Laptop Stand $49.99 3 Low Stock
USB-C Cable $19.99 0 Out of Stock
Mechanical Keyboard $159.99 87 In Stock
Wireless Mouse $79.99 234 In Stock
HTML + Stimulus
<div data-controller="table">
  <table>
    <tbody>
      <tr>
        <td>
          <button data-action="click->table#toggleExpand">
            <svg class="transition-transform">...</svg>
          </button>
        </td>
        <td>Product Name</td>
      </tr>
      <!-- Expandable Content -->
      <tr class="expandable-content hidden">
        <td colspan="5">
          Additional product details...
        </td>
      </tr>
    </tbody>
  </table>
</div>

Inline Editing with Turbo Frames

Edit cells directly • Auto-save • No page reload

User Email Role Status Actions
JD
John Doe
[email protected] Admin Active Edit
JS
Jane Smith
[email protected] Editor Inactive Edit
MJ
Mike Johnson
[email protected] Viewer Pending Edit
SW
Sarah Williams
[email protected] Manager Active Edit
HTML + Turbo Frame
<table>
  <tbody>
    <%= turbo_frame_tag "user_1" do %>
      <tr>
        <td><%= form.text_field :name %></td>
        <td><%= form.text_field :email %></td>
        <td>
          <%= form.submit "Save", 
              data: { turbo_frame: "user_1" } %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

<!-- Controller -->
def inline_edit
  render turbo_stream: turbo_stream.replace(
    "user_#{params[:id]}",
    partial: "user_row",
    locals: { user: updated_user }
  )
end

Live Search with Turbo Frames

Server-side filtering • Real-time results • Debounced requests

Name Email Role Status
JD
John Doe
[email protected] Admin Active
JS
Jane Smith
[email protected] Editor Inactive
MJ
Mike Johnson
[email protected] Viewer Pending
SW
Sarah Williams
[email protected] Manager Active
DB
David Brown
[email protected] Admin Inactive
ED
Emily Davis
[email protected] Editor Pending
CW
Chris Wilson
[email protected] Viewer Active
LA
Lisa Anderson
[email protected] Manager Inactive
HTML + Turbo Frame
<%= turbo_frame_tag "search_results" do %>
  <%= form_with url: filter_path, 
      data: { 
        turbo_frame: "search_results",
        action: "input->debounce#search" 
      } do |f| %>
    <%= f.text_field :query %>
  <% end %>

  <table id="users_table">
    <!-- Table content -->
  </table>
<% end %>

<!-- Controller -->
def filter
  @users = User.where("name LIKE ?", "%#{params[:query]}%")
  
  render turbo_stream: turbo_stream.replace(
    "users_table",
    partial: "users_table",
    locals: { users: @users }
  )
end

Pagination with Turbo Frames

Navigate pages • No full page reload • Smooth transitions

Name Email Role Status
JD
John Doe
[email protected] Admin Active
JS
Jane Smith
[email protected] Editor Inactive
MJ
Mike Johnson
[email protected] Viewer Pending
SW
Sarah Williams
[email protected] Manager Active
DB
David Brown
[email protected] Admin Inactive
Previous Next
HTML + Turbo Frame
<%= turbo_frame_tag "paginated_users" do %>
  <table>
    <!-- Table content -->
  </table>
<% end %>

<%= turbo_frame_tag "pagination" do %>
  <div class="flex justify-between">
    <%= link_to "Previous", 
        paginate_path(page: @page - 1),
        data: { turbo_frame: "paginated_users" } %>
    <%= link_to "Next", 
        paginate_path(page: @page + 1),
        data: { turbo_frame: "paginated_users" } %>
  </div>
<% end %>

<!-- Controller -->
def paginate
  @users = User.page(params[:page])
  
  render turbo_stream: [
    turbo_stream.replace("paginated_users", 
      partial: "users_table"),
    turbo_stream.replace("pagination", 
      partial: "pagination")
  ]
end

Lazy Loading Details with Turbo Frames

Click to load additional data • On-demand fetching • Loading states

Transaction Date Amount Details
Payment from Customer A
ID: 001
Jan 15, 2025 +$1200.00 View Details
Subscription Renewal
ID: 002
Jan 16, 2025 +$99.00 View Details
Refund to Customer B
ID: 003
Jan 17, 2025 $350.00 View Details
Payment from Customer C
ID: 004
Jan 18, 2025 +$2500.00 View Details
Service Fee
ID: 005
Jan 19, 2025 $45.00 View Details
HTML + Turbo Frame
<table>
  <tbody>
    <tr>
      <td>Transaction Info</td>
      <td>
        <%= turbo_frame_tag "details_1" do %>
          <%= link_to "View Details", 
              details_path(1),
              data: { turbo_frame: "details_1" } %>
        <% end %>
      </td>
    </tr>
    <tr>
      <td colspan="4">
        <%= turbo_frame_tag "details_1" do %>
          <!-- Details loaded here -->
        <% end %>
      </td>
    </tr>
  </tbody>
</table>

<!-- Controller -->
def load_details
  @transaction = Transaction.find(params[:id])
  
  render partial: "transaction_details",
         locals: { transaction: @transaction }
end

Responsive Mobile-Friendly Table

Adapts to small screens • Card layout on mobile • Powered by Tailwind

Wireless Headphones

Electronics

In Stock

Price

$299.99

Stock

145 units

Laptop Stand

Electronics

Low Stock

Price

$49.99

Stock

3 units

USB-C Cable

Electronics

Out of Stock

Price

$19.99

Stock

0 units

Mechanical Keyboard

Electronics

In Stock

Price

$159.99

Stock

87 units

Wireless Mouse

Electronics

In Stock

Price

$79.99

Stock

234 units

HTML + Tailwind Responsive
<!-- Desktop Table View -->
<div class="hidden md:block">
  <table class="min-w-full">
    <thead>
      <tr>
        <th>Product</th>
        <th>Price</th>
        <th>Actions</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Wireless Headphones</td>
        <td>$299.99</td>
        <td><a href="#">Edit</a></td>
      </tr>
    </tbody>
  </table>
</div>

<!-- Mobile Card View -->
<div class="md:hidden space-y-4">
  <div class="border rounded-lg p-4">
    <h3 class="font-semibold">Wireless Headphones</h3>
    <div class="grid grid-cols-2 gap-3 mt-3">
      <div>
        <p class="text-xs text-gray-500">Price</p>
        <p class="font-medium">$299.99</p>
      </div>
    </div>
    <div class="flex justify-end mt-3 pt-3 border-t">
      <a href="#">Edit</a>
    </div>
  </div>
</div>

Advanced Filters with Turbo Frames

Multiple filter options • Real-time updates • Powered by Turbo

Active filters: All Users

Showing 8 results

User Email Role Status Actions
JD
John Doe
[email protected] Admin Active
JS
Jane Smith
[email protected] Editor Inactive
MJ
Mike Johnson
[email protected] Viewer Pending
SW
Sarah Williams
[email protected] Manager Active
DB
David Brown
[email protected] Admin Inactive
ED
Emily Davis
[email protected] Editor Pending
CW
Chris Wilson
[email protected] Viewer Active
LA
Lisa Anderson
[email protected] Manager Inactive
HTML + Turbo Frame + Stimulus
<div data-controller="table">
  <!-- Filters -->
  <div class="mb-6 p-4 bg-gray-50 rounded-lg">
    <input 
      type="text" 
      data-action="input->table#search"
      placeholder="Search..."
    >
    
    <select data-action="change->table#search">
      <option value="">All Roles</option>
      <option value="admin">Admin</option>
    </select>
  </div>

  <!-- Results -->
  <table>
    <tbody>
      <tr data-table-target="row">
        <td>John Doe</td>
        <td>Admin</td>
      </tr>
    </tbody>
  </table>

  <!-- Empty State -->
  <div data-empty-state class="hidden">
    No matching results
  </div>
</div>