Senren UI
Components / Table

Table

Stable

tabular data display

Project status
Project Status Owner
Docs site Active Mai
Billing Queued An
Search Review Linh

Usage example #

Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.

app/views/.../table_example.html.erb
<div class="w-full">
  <%= render Senren::TableComponent.new(
    caption: "Project status",
    columns: [
      { key: :project, label: "Project" },
      { key: :status, label: "Status" },
      { key: :owner, label: "Owner" }
    ],
    rows: [
      { project: "Docs site", status: "Active", owner: "Mai" },
      { project: "Billing", status: "Queued", owner: "An" },
      { project: "Search", status: "Review", owner: "Linh" }
    ]
  ) %>
</div>

Install this component #

Copy the official component into your app

Use this when you want the Senren-maintained implementation copied into app/components/senren.

Terminal
bin/rails senren:add table

Create a custom component with the same conventions

Use this when you need an app-specific static component that follows Senren's ViewComponent structure.

Terminal
bin/rails generate senren:component table --no-client

At a glance #

Category Data
Class name Senren::TableComponent
Stimulus
Variants default, compact
Depends on
Pairs with pagination, dropdown_menu, badge

Source #

app/components/senren/table_component.html.erb
<div <%= tag.attributes(**root_attrs("w-full overflow-hidden rounded-(--senren-radius) border border-[hsl(var(--senren-border))]")) %>>
  <table class="w-full border-collapse text-left text-sm <%= self.class::VARIANTS[variant] %>">
    <% if caption.present? %>
      <caption class="sr-only"><%= caption %></caption>
    <% end %>
    <thead class="bg-[hsl(var(--senren-muted)/0.6)] text-[hsl(var(--senren-muted-foreground))]">
      <tr>
        <% columns.each do |column| %>
          <th scope="col" class="px-4 py-3 font-medium"><%= column_label(column) %></th>
        <% end %>
      </tr>
    </thead>
    <tbody class="divide-y divide-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background))] text-[hsl(var(--senren-foreground))]">
      <% rows.each do |row| %>
        <tr class="transition-colors hover:bg-[hsl(var(--senren-muted)/0.4)]">
          <% columns.each do |column| %>
            <td class="px-4 py-3"><%= cell_value(row, column) %></td>
          <% end %>
        </tr>
      <% end %>
      <% if rows.empty? && content? %>
        <tr><td class="px-4 py-6" colspan="<%= columns.size.nonzero? || 1 %>"><%= content %></td></tr>
      <% end %>
    </tbody>
  </table>
</div>
app/components/senren/table_component.rb
# frozen_string_literal: true

module Senren
  class TableComponent < BaseComponent
    VARIANTS = {
      default: '',
      compact: 'text-xs'
    }.freeze
    SIZES = { md: '' }.freeze

    def initialize(columns: [], rows: [], caption: nil, variant: :default, class_name: nil, **html)
      super(variant: variant, size: :md, class_name: class_name, **html)
      @columns = Array(columns)
      @rows = Array(rows)
      @caption = caption
    end

    attr_reader :columns, :rows, :caption

    def cell_value(row, column)
      key = column_key(column)
      row.is_a?(Hash) ? (row[key] || row[key.to_s]) : row[key.to_i]
    end

    def column_label(column)
      column.is_a?(Hash) ? (column[:label] || column['label']) : column.to_s.titleize
    end

    private

    def column_key(column)
      column.is_a?(Hash) ? (column[:key] || column['key']) : column
    end
  end
end

AI agent rules #

Use for

  • +tabular data display

Avoid

  • -layout - use grid/flex

Accessibility #

  • Use real th/scope; caption for table summary.