Table — Maquina Components
Table component for Rails. Responsive data tables with sorting and sticky headers. ERB partial with Tailwind CSS. Copy, paste, customize.
Quick Reference
Parts
| Partial | Element | Description |
|---|---|---|
components/table |
<table> |
Main table container |
components/table/header |
<thead> |
Table header section |
components/table/body |
<tbody> |
Table body section |
components/table/footer |
<tfoot> |
Table footer section |
components/table/row |
<tr> |
Table row |
components/table/head |
<th> |
Header cell |
components/table/cell |
<td> |
Body cell |
components/table/caption |
<caption> |
Table caption |
Parameters
table
| Parameter | Type | Default | Description |
|---|---|---|---|
container |
Boolean | true |
Wrap in scrollable container |
variant |
Symbol | :default |
Table style variant |
table_variant |
Symbol | nil |
Additional table styling |
css_classes |
String | "" |
Additional CSS classes |
**html_options |
Hash | {} |
HTML attributes |
table/header
| Parameter | Type | Default | Description |
|---|---|---|---|
sticky |
Boolean | false |
Sticky header on scroll |
css_classes |
String | "" |
Additional CSS classes |
**html_options |
Hash | {} |
HTML attributes |
table/head
| Parameter | Type | Default | Description |
|---|---|---|---|
sortable |
Boolean | false |
Show sort indicator |
sorted |
Symbol | nil |
Sort direction (:asc, :desc) |
align |
Symbol | :left |
Text alignment (:left, :center, :right) |
css_classes |
String | "" |
Additional CSS classes |
**html_options |
Hash | {} |
HTML attributes |
table/cell
| Parameter | Type | Default | Description |
|---|---|---|---|
align |
Symbol | :left |
Text alignment (:left, :center, :right) |
css_classes |
String | "" |
Additional CSS classes |
**html_options |
Hash | {} |
HTML attributes |
table/row
| Parameter | Type | Default | Description |
|---|---|---|---|
selected |
Boolean | false |
Highlight as selected |
css_classes |
String | "" |
Additional CSS classes |
**html_options |
Hash | {} |
HTML attributes |
Data Attributes
Component Identifiers
| Attribute | Element | Description |
|---|---|---|
data-component="table" |
<table> |
Main component identifier |
data-table-part="container" |
Container div | Scrollable wrapper |
data-table-part="header" |
<thead> |
Header section |
data-table-part="body" |
<tbody> |
Body section |
data-table-part="footer" |
<tfoot> |
Footer section |
data-table-part="row" |
<tr> |
Table row |
data-table-part="head" |
<th> |
Header cell |
data-table-part="cell" |
<td> |
Body cell |
data-table-part="caption" |
<caption> |
Table caption |
State Attributes
| Attribute | Description |
|---|---|
data-selected="true" |
Selected row |
data-sticky="true" |
Sticky header |
data-sortable="true" |
Sortable column |
data-sorted="asc" |
Ascending sort |
data-sorted="desc" |
Descending sort |
data-align="center" |
Center alignment |
data-align="right" |
Right alignment |
Basic Usage
Using Partials
<%= render "components/table" do %>
<%= render "components/table/header" do %>
<%= render "components/table/row" do %>
<%= render "components/table/head" do %>Name<% end %>
<%= render "components/table/head" do %>Email<% end %>
<%= render "components/table/head" do %>Role<% end %>
<% end %>
<% end %>
<%= render "components/table/body" do %>
<% @users.each do |user| %>
<%= render "components/table/row" do %>
<%= render "components/table/cell" do %><%= user.name %><% end %>
<%= render "components/table/cell" do %><%= user.email %><% end %>
<%= render "components/table/cell" do %><%= user.role %><% end %>
<% end %>
<% end %>
<% end %>
<% end %>
Using Data Attributes Directly
<div data-table-part="container">
<table data-component="table">
<thead data-table-part="header">
<tr data-table-part="row">
<th data-table-part="head">Name</th>
<th data-table-part="head">Email</th>
</tr>
</thead>
<tbody data-table-part="body">
<% @users.each do |user| %>
<tr data-table-part="row">
<td data-table-part="cell"><%= user.name %></td>
<td data-table-part="cell"><%= user.email %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
Examples
With Caption
<%= render "components/table" do %>
<%= render "components/table/caption" do %>
A list of recent invoices
<% end %>
<%= render "components/table/header" do %>
<!-- ... -->
<% end %>
<%= render "components/table/body" do %>
<!-- ... -->
<% end %>
<% end %>
Sticky Header
<%= render "components/table" do %>
<%= render "components/table/header", sticky: true do %>
<!-- Header stays visible on scroll -->
<% end %>
<%= render "components/table/body" do %>
<!-- ... -->
<% end %>
<% end %>
Sortable Columns
<%= render "components/table/header" do %>
<%= render "components/table/row" do %>
<%= render "components/table/head", sortable: true, sorted: :asc do %>
Name
<% end %>
<%= render "components/table/head", sortable: true do %>
Email
<% end %>
<%= render "components/table/head" do %>
Actions
<% end %>
<% end %>
<% end %>
Column Alignment
<%= render "components/table/row" do %>
<%= render "components/table/cell" do %>Product Name<% end %>
<%= render "components/table/cell", align: :center do %>Quantity<% end %>
<%= render "components/table/cell", align: :right do %>$99.00<% end %>
<% end %>
Selected Rows
<% @items.each do |item| %>
<%= render "components/table/row", selected: item.selected? do %>
<%= render "components/table/cell" do %><%= item.name %><% end %>
<% end %>
<% end %>
With Actions Column
<%= render "components/table/row" do %>
<%= render "components/table/cell" do %><%= user.name %><% end %>
<%= render "components/table/cell" do %><%= user.email %><% end %>
<%= render "components/table/cell", align: :right do %>
<%= dropdown_menu do |menu| %>
<% menu.trigger as_child: true do %>
<button type="button" data-component="button" data-variant="ghost" data-size="icon-sm">
<%= icon_for :more_horizontal, class: "size-4" %>
</button>
<% end %>
<% menu.content align: :end do %>
<% menu.item "Edit", href: edit_user_path(user), icon: :pencil %>
<% menu.separator %>
<% menu.item "Delete", href: user_path(user), method: :delete, icon: :trash, variant: :destructive %>
<% end %>
<% end %>
<% end %>
<% end %>
Real-World Patterns
In a Card
<%= render "components/card" do %>
<%= render "components/card/header", layout: :row do %>
<div>
<%= render "components/card/title", text: "Users" %>
<%= render "components/card/description", text: "Manage your team members." %>
</div>
<%= render "components/card/action" do %>
<%= link_to "Add User", new_user_path, data: { component: "button", variant: "primary", size: "sm" } %>
<% end %>
<% end %>
<%= render "components/card/content", spacing: :full do %>
<%= render "components/table", container: false do %>
<!-- Table content -->
<% end %>
<% end %>
<%= render "components/card/footer", align: :between do %>
<p class="text-sm text-muted-foreground"><%= @pagy.count %> users</p>
<%= pagination_nav(@pagy) %>
<% end %>
<% end %>
With Empty State
<%= render "components/table" do %>
<%= render "components/table/header" do %>
<%= render "components/table/row" do %>
<%= render "components/table/head" do %>Name<% end %>
<%= render "components/table/head" do %>Email<% end %>
<% end %>
<% end %>
<%= render "components/table/body" do %>
<% if @users.empty? %>
<tr>
<td colspan="2">
<%= empty_list_state(resource: "users", new_path: new_user_path) %>
</td>
</tr>
<% else %>
<% @users.each do |user| %>
<%= render "components/table/row" do %>
<%= render "components/table/cell" do %><%= user.name %><% end %>
<%= render "components/table/cell" do %><%= user.email %><% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
With Selection Checkboxes
<%= render "components/table/header" do %>
<%= render "components/table/row" do %>
<%= render "components/table/head", css_classes: "w-12" do %>
<input type="checkbox" data-component="checkbox" data-action="change->bulk-select#toggleAll">
<% end %>
<%= render "components/table/head" do %>Name<% end %>
<% end %>
<% end %>
<%= render "components/table/body" do %>
<% @items.each do |item| %>
<%= render "components/table/row" do %>
<%= render "components/table/cell" do %>
<input type="checkbox" name="selected[]" value="<%= item.id %>" data-component="checkbox" data-bulk-select-target="checkbox">
<% end %>
<%= render "components/table/cell" do %><%= item.name %><% end %>
<% end %>
<% end %>
<% end %>
Theme Variables
var(--border)
var(--background)
var(--foreground)
var(--muted)
var(--muted-foreground)
var(--accent)
Customization
Striped Rows
Add to your CSS:
[data-component="table"][data-variant="striped"] [data-table-part="body"] [data-table-part="row"]:nth-child(even) {
background-color: var(--muted);
}
Usage:
<%= render "components/table", table_variant: :striped do %>
<!-- ... -->
<% end %>
Compact Table
<%= render "components/table", css_classes: "[&_th]:py-2 [&_td]:py-2" do %>
<!-- ... -->
<% end %>
Without Container (No Scroll)
<%= render "components/table", container: false do %>
<!-- Table will not have horizontal scroll wrapper -->
<% end %>
Accessibility
- Uses semantic
<table>,<thead>,<tbody>,<th>,<td>elements - Headers use
scope="col"for column headers - Caption provides table description for screen readers
- Sortable columns indicate sort state via
aria-sort - Selected rows can use
aria-selected
File Structure
app/views/components/
├── _table.html.erb
└── table/
├── _header.html.erb
├── _body.html.erb
├── _footer.html.erb
├── _row.html.erb
├── _head.html.erb
├── _cell.html.erb
└── _caption.html.erb
app/assets/stylesheets/table.css
docs/table.md