Datatable Rails Components

Display tabular data in a scrollable, sortable table with filters. All examples use Turbo Frames for seamless server-side sorting and filtering without full page reloads. Includes row selection with bulk actions and pagination integration.

Installation

1. Stimulus Controller

The datatable controller handles sorting via Turbo Frames, preserving filter values and integrating with pagination.

This code is available to Pro users only.

// Hey curious person!
// You seem to be sneaking around the code...
// I hope you're enjoying the components!
// Have a great day!

class SecretMessage {
  constructor() {
    this.message = "Thanks for checking out Rails Blocks!";
    this.compliment = "You're awesome!";
  }

  reveal() {
    console.log(this.message);
    return this.compliment;
  }
}

const secret = new SecretMessage();
secret.reveal();

2. Sort Indicator Partial

Create a reusable sort indicator partial that shows sort direction and loading states:

This code is available to Pro users only.

// Hey curious person!
// You seem to be sneaking around the code...
// I hope you're enjoying the components!
// Have a great day!

class SecretMessage {
  constructor() {
    this.message = "Thanks for checking out Rails Blocks!";
    this.compliment = "You're awesome!";
  }

  reveal() {
    console.log(this.message);
    return this.compliment;
  }
}

const secret = new SecretMessage();
secret.reveal();

Examples

Basic Datatable

A clean datatable with sortable column headers. Click any column header to sort by that column. The sort direction toggles between ascending and descending on subsequent clicks.

Name
Email
Role
Status
Created
Aaron Phillips aaron@example.com Viewer Pending May 28, 2023
Alex Johnson alex@example.com Admin Active Jan 15, 2026
Amanda White amanda@example.com Viewer Active Feb 15, 2025
Andrew Scott andrew@example.com Viewer Inactive Mar 12, 2024
Ashley Young ashley@example.com Admin Pending Jun 08, 2024
Brandon Mitchell brandon@example.com Viewer Inactive Sep 28, 2023
Brian Walker brian@example.com Viewer Inactive Sep 30, 2024
Brittany Collins brittany@example.com Editor Pending Jan 10, 2023
Christina Evans christina@example.com Admin Active Feb 05, 2023
Christopher Lee christopher@example.com Editor Inactive Mar 28, 2025
Daniel Harris daniel@example.com Editor Pending Jan 02, 2025
Danielle Campbell danielle@example.com Viewer Active Apr 20, 2023
David Martinez david@example.com Viewer Active May 18, 2025
Elizabeth Wright elizabeth@example.com Editor Active Apr 20, 2024
Emily Davis emily@example.com Editor Active Oct 15, 2025
Heather Turner heather@example.com Editor Active Jun 05, 2023
James Wilson james@example.com Viewer Inactive Sep 20, 2025
Jennifer Garcia jennifer@example.com Admin Active Apr 10, 2025
Jeremy Edwards jeremy@example.com Viewer Active Jan 28, 2023
Joshua King joshua@example.com Viewer Active May 28, 2024
Justin Nelson justin@example.com Viewer Active Nov 15, 2023
Kevin Lewis kevin@example.com Admin Active Nov 18, 2024
Laura Baker laura@example.com Editor Pending Dec 20, 2023
Lisa Anderson lisa@example.com Admin Active Aug 10, 2025
Matthew Allen matthew@example.com Editor Active Jul 15, 2024
Megan Green megan@example.com Viewer Active Feb 05, 2024
Michael Chen michael@example.com Viewer Pending Nov 01, 2025
Michelle Clark michelle@example.com Viewer Active Dec 25, 2024
Nathan Parker nathan@example.com Editor Inactive Mar 12, 2023
Nicole Hall nicole@example.com Viewer Active Aug 22, 2024
Patricia Taylor patricia@example.com Editor Pending Jun 22, 2025
Rachel Carter rachel@example.com Editor Active Oct 08, 2023
Robert Brown robert@example.com Viewer Active Jul 05, 2025
Ryan Adams ryan@example.com Admin Active Jan 28, 2024
Samantha Perez samantha@example.com Viewer Active Aug 20, 2023
Sarah Miller sarah@example.com Editor Active Dec 28, 2025
Stephanie Robinson stephanie@example.com Editor Active Oct 10, 2024
Tyler Roberts tyler@example.com Admin Active Jul 12, 2023

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

This code is available to Pro users only.

# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!

class SecretMessage
  def initialize
    @message = "Thanks for checking out Rails Blocks!"
    @compliment = "You're awesome!"
  end

  def reveal
    puts @message
    @compliment
  end
end

secret = SecretMessage.new
secret.reveal

Datatable with Row Selection & Pagination

Combines sortable columns with checkbox selection for bulk operations. Select rows to reveal the action bar. Supports shift-click range selection and "select all pages" for virtual selection across paginated data. Uses Checkbox Select All and Pagination.

This component has to be viewed in fullscreen

Preview
Name
Email
Role
Status
Created
Aaron Phillips aaron@example.com Viewer Pending May 28, 2023
Alex Johnson alex@example.com Admin Active Jan 15, 2026
Amanda White amanda@example.com Viewer Active Feb 15, 2025
Andrew Scott andrew@example.com Viewer Inactive Mar 12, 2024
Ashley Young ashley@example.com Admin Pending Jun 08, 2024
Brandon Mitchell brandon@example.com Viewer Inactive Sep 28, 2023
Brian Walker brian@example.com Viewer Inactive Sep 30, 2024
Brittany Collins brittany@example.com Editor Pending Jan 10, 2023

Showing 1–8 of 38

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

This code is available to Pro users only.

# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!

class SecretMessage
  def initialize
    @message = "Thanks for checking out Rails Blocks!"
    @compliment = "You're awesome!"
  end

  def reveal
    puts @message
    @compliment
  end
end

secret = SecretMessage.new
secret.reveal

Datatable with Filters

A datatable with dropdown filters, search input, and server-side filtering via Turbo Frames. Filters auto-submit on change using the included Autosubmit controller. Uses Select and Pagination.

This component has to be viewed in fullscreen

Preview
Product
Category
Status
Price
Stock
Updated
AD
Adidas Ultraboost
Footwear Low Stock $190 8 units Jun 13, 2024
AD
Adidas Ultraboost v2
Footwear Low Stock $190 18 units May 29, 2024
AD
Adidas Ultraboost v3
Footwear Low Stock $190 28 units May 14, 2024
AD
Adidas Ultraboost v4
Footwear Low Stock $190 38 units Apr 29, 2024
AD
Adidas Ultraboost v5
Footwear Low Stock $190 48 units Apr 14, 2024
AI
AirPods Pro
Electronics Low Stock $249 12 units Sep 18, 2025
AI
AirPods Pro v2
Electronics Low Stock $249 22 units Sep 03, 2025
AI
AirPods Pro v3
Electronics Low Stock $249 32 units Aug 19, 2025
AI
AirPods Pro v4
Electronics Low Stock $249 42 units Aug 04, 2025
AI
AirPods Pro v5
Electronics Low Stock $249 52 units Jul 20, 2025

Showing 1–10 of 75

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

This code is available to Pro users only.

// Hey curious person!
// You seem to be sneaking around the code...
// I hope you're enjoying the components!
// Have a great day!

class SecretMessage {
  constructor() {
    this.message = "Thanks for checking out Rails Blocks!";
    this.compliment = "You're awesome!";
  }

  reveal() {
    console.log(this.message);
    return this.compliment;
  }
}

const secret = new SecretMessage();
secret.reveal();

This code is available to Pro users only.

# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!

class SecretMessage
  def initialize
    @message = "Thanks for checking out Rails Blocks!"
    @compliment = "You're awesome!"
  end

  def reveal
    puts @message
    @compliment
  end
end

secret = SecretMessage.new
secret.reveal

Datatable with Multi-Select Filters

Multi-select dropdown filters for complex filtering scenarios. Filter by multiple values simultaneously with active filters shown as removable tags. Uses Select in multi-select mode.

This component has to be viewed in fullscreen

Preview
Project
Team
Priority Status
Progress
Deadline
Analytics Integration Engineering Low Completed
100%
Nov 15, 2024
API Documentation Engineering Low In Progress
60%
Dec 20, 2024
Brand Guidelines Design Medium Completed
100%
Dec 01, 2024
Customer Portal Engineering Medium On Hold
25%
Mar 01, 2025
Design System Design High In Progress
40%
Feb 15, 2025
Email Templates Marketing Low Planning
10%
Feb 01, 2025
Mobile App v2 Engineering High In Progress
45%
Jan 15, 2025
Performance Review Engineering Medium Planning
5%
Jan 20, 2025
Product Launch Marketing High In Progress
65%
Dec 28, 2024
Q1 Campaign Marketing High Planning
20%
Jan 31, 2025
Sales Dashboard Engineering Medium In Progress
35%
Jan 10, 2025
Security Audit Engineering High On Hold
50%
Dec 25, 2024
Social Media Kit Design Low In Progress
80%
Dec 18, 2024
User Research Design Medium Completed
100%
Nov 30, 2024
Website Redesign Engineering High In Progress
75%
Dec 30, 2024

Showing 15 of 15 projects

This code is available to Pro users only.

<!-- Hey curious person! -->
<!-- You seem to be sneaking around the code... -->
<!-- I hope you're enjoying the components! -->
<!-- Have a great day! -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Secret Message from Rails Blocks</title>
  <style>
    .secret-message {
      font-family: 'Comic Sans MS', cursive;
      text-align: center;
      padding: 2rem;
      background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
      border-radius: 10px;
      box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    }
    .wiggle { animation: wiggle 0.5s ease-in-out infinite; }
    @keyframes wiggle {
      0%, 100% { transform: rotate(0deg); }
      25% { transform: rotate(1deg); }
      75% { transform: rotate(-1deg); }
    }
  </style>
</head>
<body>
  <div class="secret-message">
    <h1>🎉 Hello, Code Detective! 🕵️</h1>
    <p>Thanks for checking out Rails Blocks!</p>
    <p>You're clearly someone who pays attention to details.</p>
    <p>That's exactly the kind of developer we love!</p>

    <div class="cta-section">
      <button class="awesome-btn wiggle">You're awesome!</button>
      <p><small>Seriously, keep being curious! 🚀</small></p>
    </div>

    <footer>
      <p>Built with ❤️ by the Rails Blocks team</p>
      <p>Now go build something amazing!</p>
    </footer>
  </div>

  <script>
    console.log("🎊 Bonus points for opening the console!");
    console.log("Keep exploring and happy coding! 💻");
  </script>
</body>
</html>

This code is available to Pro users only.

# Hey curious person!
# You seem to be sneaking around the code...
# I hope you're enjoying the components!
# Have a great day!

class SecretMessage
  def initialize
    @message = "Thanks for checking out Rails Blocks!"
    @compliment = "You're awesome!"
  end

  def reveal
    puts @message
    @compliment
  end
end

secret = SecretMessage.new
secret.reveal

Configuration

The datatable controller handles sorting via Turbo Frames with URL-based state management. It preserves filter form values when sorting and integrates seamlessly with pagination.

Controller Values

Configure the datatable controller with these values:

Value Type Default Description
frame String "" Turbo Frame ID for navigation. If empty, falls back to full page reload.
sortColumn String "" Currently sorted column name (used for toggle logic)
sortDirection String "asc" Current sort direction ("asc" or "desc")
columnParam String "column" URL parameter name for the sort column
directionParam String "direction" URL parameter name for the sort direction

Targets

Target Description Required
header Sortable column headers. Must have data-column attribute with the column name. Required
filterForm Optional filter form. Values are preserved in URL when sorting. Optional

Actions

Action Description Usage
sort Sorts by the clicked column, toggling direction if same column. Shows loading spinner and navigates via Turbo Frame. click->datatable#sort

Rails Controller Example

Here's how to set up your Rails controller to handle server-side sorting:

class UsersController < ApplicationController
  def index
    # Get sort parameters from URL
    sort_column = params[:column] || "name"
    sort_direction = params[:direction] || "asc"

    # Validate sort column to prevent SQL injection
    allowed_columns = %w[name email role created_at]
    sort_column = "name" unless allowed_columns.include?(sort_column)
    sort_direction = "asc" unless %w[asc desc].include?(sort_direction)

    # Apply sorting to your query
    @users = User.order("#{sort_column} #{sort_direction}")

    # Pass sort state to the view
    @sort_column = sort_column
    @sort_direction = sort_direction

    # Respond to Turbo Frame requests
    respond_to do |format|
      format.html
      format.turbo_stream
    end
  end
end

Basic View Setup

Minimal HTML structure for a sortable datatable with Turbo Frame support:

<turbo-frame id="users-table">
  <div data-controller="datatable"
       data-datatable-frame-value="users-table"
       data-datatable-sort-column-value="<%%= @sort_column %>"
       data-datatable-sort-direction-value="<%%= @sort_direction %>"
       data-datatable-column-param-value="column"
       data-datatable-direction-param-value="direction">

    <table>
      <thead>
        <tr>
          <!-- Sortable column -->
          <th data-datatable-target="header"
              data-column="name"
              data-action="click->datatable#sort"
              class="cursor-pointer">
            <span>Name</span>
            <%%= render "shared/sort_indicator",
                  column: "name",
                  sort_column: @sort_column,
                  sort_direction: @sort_direction %>
          </th>

          <!-- Non-sortable column -->
          <th>Status</th>
        </tr>
      </thead>
      <tbody>
        <%% @users.each do |user| %>
          <tr>
            <td><%%= user.name %></td>
            <td><%%= user.status %></td>
          </tr>
        <%% end %>
      </tbody>
    </table>

    <!-- Pagination -->
    <%%= render "shared/pagy_nav",
          pagy: @pagy,
          frame_id: "users-table",
          preserve_params: request.query_parameters %>
  </div>
</turbo-frame>

Combining with Other Components

The datatable works seamlessly with other Rails Blocks components:

Pagination – Add pagination controls with Pagy integration
Checkbox Select All – Row selection with bulk actions and "select all pages"
Combobox – Multi-select filters for complex filtering scenarios
Scroll Area – Custom scrollbars for better UX on long tables

Features

  • Turbo Frame Support: Seamless integration with Hotwire Turbo for instant sorting without full page reloads
  • Filter Preservation: Sort operations automatically preserve filter form values in the URL
  • Loading States: Built-in loading spinner on column headers during sort operations
  • Select All Pages: Virtual selection across paginated data without loading all rows
  • URL State Management: Sort state is persisted in URL parameters for bookmarking and sharing
  • Empty State Handling: Built-in support for showing empty state when filters return no results

Table of contents

Get notified when new components come out