Table

A responsive table component for displaying tabular data.

No JavaScript Required
NameEmailRoleStatus
Alice Johnsonalice@example.comAdminActive
Bob Smithbob@example.comUserActive
Charlie Browncharlie@example.comManagerInactive
Diana Princediana@example.comUserActive
table/default
<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Email" } %>
      <% row.with_head { "Role" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Olivia Martin" } %>
      <% row.with_cell { "olivia@example.com" } %>
      <% row.with_cell { "Owner" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new { "Active" } %>
      <% end %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Jackson Lee" } %>
      <% row.with_cell { "jackson@example.com" } %>
      <% row.with_cell { "Member" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new(variant: :secondary) { "Invited" } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Installation

Add the component to your project:

rails generate shadcn:add table

Usage

The Table component uses a slot-based API with nested components for header, body, footer, and caption.

<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Email" } %>
      <% row.with_head { "Role" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Olivia Martin" } %>
      <% row.with_cell { "olivia@example.com" } %>
      <% row.with_cell { "Owner" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new { "Active" } %>
      <% end %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Jackson Lee" } %>
      <% row.with_cell { "jackson@example.com" } %>
      <% row.with_cell { "Member" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new(variant: :secondary) { "Invited" } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Examples

With Caption

Add a caption to describe the table contents.

A list of your recent transactions.
TransactionAmountStatus
Payment$250.00Completed
Refund$150.00Processing
<%= render Shadcn::TableComponent.new do |table| %>
  <% table.with_caption { "A list of your recent transactions." } %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Transaction" } %>
      <% row.with_head { "Amount" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Payment" } %>
      <% row.with_cell { "$250.00" } %>
      <% row.with_cell { "Completed" } %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Refund" } %>
      <% row.with_cell { "$150.00" } %>
      <% row.with_cell { "Processing" } %>
    <% end %>
  <% end %>
<% end %>

With Footer

Use a footer row for totals or summary information.

ItemQuantityPrice
Product A2$20.00
Product B1$15.00
Total3$35.00
<%= render Shadcn::TableComponent.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Item" } %>
      <% row.with_head { "Quantity" } %>
      <% row.with_head { "Price" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Product A" } %>
      <% row.with_cell { "2" } %>
      <% row.with_cell { "$20.00" } %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Product B" } %>
      <% row.with_cell { "1" } %>
      <% row.with_cell { "$15.00" } %>
    <% end %>
  <% end %>
  <% table.with_footer do |footer| %>
    <% footer.with_row do |row| %>
      <% row.with_cell { "Total" } %>
      <% row.with_cell { "3" } %>
      <% row.with_cell { "$35.00" } %>
    <% end %>
  <% end %>
<% end %>

Invoices Table

A complete example with caption, footer, and custom cell styling.

A list of your recent invoices.
InvoiceStatusMethodAmount
INV001PaidCredit Card$250.00
INV002PendingPayPal$150.00
INV003UnpaidBank Transfer$350.00
Total$750.00
<%= render Shadcn::TableComponent.new do |table| %>
  <% table.with_caption { "A list of your recent invoices." } %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Invoice" } %>
      <% row.with_head { "Status" } %>
      <% row.with_head { "Method" } %>
      <% row.with_head(class: "text-right") { "Amount" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell(class: "font-medium") { "INV001" } %>
      <% row.with_cell { "Paid" } %>
      <% row.with_cell { "Credit Card" } %>
      <% row.with_cell(class: "text-right") { "$250.00" } %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell(class: "font-medium") { "INV002" } %>
      <% row.with_cell { "Pending" } %>
      <% row.with_cell { "PayPal" } %>
      <% row.with_cell(class: "text-right") { "$150.00" } %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell(class: "font-medium") { "INV003" } %>
      <% row.with_cell { "Unpaid" } %>
      <% row.with_cell { "Bank Transfer" } %>
      <% row.with_cell(class: "text-right") { "$350.00" } %>
    <% end %>
  <% end %>
  <% table.with_footer do |footer| %>
    <% footer.with_row do |row| %>
      <% row.with_cell(colspan: 3) { "Total" } %>
      <% row.with_cell(class: "text-right") { "$750.00" } %>
    <% end %>
  <% end %>
<% end %>

Selected Row

Highlight a selected row using the selected prop.

NameStatus
Item 1Active
Item 2Selected
Item 3Active
<%= render Shadcn::TableComponent.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Item 1" } %>
      <% row.with_cell { "Active" } %>
    <% end %>
    <% body.with_row(selected: true) do |row| %>
      <% row.with_cell { "Item 2" } %>
      <% row.with_cell { "Selected" } %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Item 3" } %>
      <% row.with_cell { "Active" } %>
    <% end %>
  <% end %>
<% end %>

Striped Rows

IDNameDepartmentSalary
E001John DoeEngineering$85,000
E002Jane SmithDesign$78,000
E003Mike JohnsonMarketing$72,000
E004Sarah WilliamsSales$68,000
E005Tom BrownEngineering$92,000
table/striped
<% employees = [
  { id: "EMP001", name: "Olivia Martin", department: "Engineering", salary: "$120,000" },
  { id: "EMP002", name: "Jackson Lee", department: "Design", salary: "$98,000" },
  { id: "EMP003", name: "Isabella Nguyen", department: "Product", salary: "$112,000" }
] %>

<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "ID" } %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Department" } %>
      <% row.with_head { "Salary" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% employees.each_with_index do |employee, index| %>
      <% body.with_row(class_name: index.even? ? "bg-muted/50" : "") do |row| %>
        <% row.with_cell { employee[:id] } %>
        <% row.with_cell { employee[:name] } %>
        <% row.with_cell { employee[:department] } %>
        <% row.with_cell { employee[:salary] } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

With Actions

UserEmailRoleActions
Alice Johnsonalice@example.comAdmin
Bob Smithbob@example.comUser
Charlie Browncharlie@example.comManager
Diana Princediana@example.comUser
table/with_actions
<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "User" } %>
      <% row.with_head { "Email" } %>
      <% row.with_head { "Role" } %>
      <% row.with_head(class_name: "text-right") { "Actions" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Olivia Martin" } %>
      <% row.with_cell { "olivia@example.com" } %>
      <% row.with_cell { "Owner" } %>
      <% row.with_cell(class_name: "text-right") do %>
        <div class="flex justify-end gap-2">
          <%= render Shadcn::Button.new(variant: :ghost, class_name: "h-8 px-2 text-xs") { "Edit" } %>
          <%= render Shadcn::Button.new(variant: :ghost, class_name: "h-8 px-2 text-xs text-destructive") { "Delete" } %>
        </div>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Selectable Rows

TaskPriorityAssigneeDue Date
Implement authenticationHighAlice2024-01-15
Design landing pageMediumBob2024-01-18
Write documentationLowCharlie2024-01-20
Fix login bugHighDiana2024-01-16
table/selectable
<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Email" } %>
      <% row.with_head { "Role" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Olivia Martin" } %>
      <% row.with_cell { "olivia@example.com" } %>
      <% row.with_cell { "Owner" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new { "Active" } %>
      <% end %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Jackson Lee" } %>
      <% row.with_cell { "jackson@example.com" } %>
      <% row.with_cell { "Member" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new(variant: :secondary) { "Invited" } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Empty State

No results found.
NameEmailRoleStatus

No users found.

table/empty_state
<%= render Shadcn::Table.new do |table| %>
  <% table.with_header do |header| %>
    <% header.with_row do |row| %>
      <% row.with_head { "Name" } %>
      <% row.with_head { "Email" } %>
      <% row.with_head { "Role" } %>
      <% row.with_head { "Status" } %>
    <% end %>
  <% end %>
  <% table.with_body do |body| %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Olivia Martin" } %>
      <% row.with_cell { "olivia@example.com" } %>
      <% row.with_cell { "Owner" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new { "Active" } %>
      <% end %>
    <% end %>
    <% body.with_row do |row| %>
      <% row.with_cell { "Jackson Lee" } %>
      <% row.with_cell { "jackson@example.com" } %>
      <% row.with_cell { "Member" } %>
      <% row.with_cell do %>
        <%= render Shadcn::Badge.new(variant: :secondary) { "Invited" } %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

API Reference

TableComponent

Main table container with automatic overflow scrolling.

Slots

Slot Type Description
with_caption TableCaptionComponent Optional caption describing the table
with_header TableHeaderComponent Table header with column headings
with_body TableBodyComponent Table body with data rows
with_footer TableFooterComponent Optional footer for totals or summary

TableHeaderComponent

Contains header rows with column headings.

Slots

Slot Type Description
with_row TableRowComponent Header row containing heading cells

TableBodyComponent

Contains data rows.

Slots

Slot Type Description
with_row TableRowComponent Data row containing cells

TableFooterComponent

Contains footer rows, typically for totals.

Slots

Slot Type Description
with_row TableRowComponent Footer row containing cells

TableRowComponent

Prop Type Default Description
selected Boolean false Highlights the row with selected state

Slots

Slot Type Description
with_head TableHeadComponent Header cell (use in header rows)
with_cell TableCellComponent Data cell (use in body/footer rows)

TableHeadComponent

Header cell with appropriate styling for column headings. Accepts standard HTML attributes like class, colspan, rowspan.

TableCellComponent

Data cell with appropriate styling. Accepts standard HTML attributes like class, colspan, rowspan.

TableCaptionComponent

Caption text describing the table's purpose or contents.

Accessibility

  • Uses semantic HTML table elements (<table>, <thead>, <tbody>, <tfoot>)
  • Header cells use <th> elements for proper screen reader association
  • Caption provides context for assistive technologies
  • Responsive overflow container with horizontal scrolling on small screens
  • Row hover states provide visual feedback
  • Selected rows use data-state="selected" attribute for programmatic access