Table

Semantic tabular data with typed alignment and scope.

Source
app/components/nitro_kit/table.rb
API
NitroKit::Table.new { |table| table.thead; table.tbody }

Semantic structure

Captions, column headers, row headers, and alignment remain explicit in Ruby and HTML.

Workspace members

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-members",
  table_html: { id: "gallery-table-members-element" }
) do |table|
  table.caption("Workspace members")
  table.thead do
    table.tr do
      table.th("Name")
      table.th("Role")
      table.th("Status", align: :right)
    end
  end
  table.tbody do
    Gallery::Data.members.each do |member|
      table.tr do
        table.th(member.name, scope: :row)
        table.td(member.role.to_s.humanize)
        table.td(member.status.to_s.humanize, align: :right)
      end
    end
  end
end

Data and actions

Badges and compact actions compose inside cells without weakening table semantics.

Invoice history

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-invoices",
  table_html: { id: "gallery-table-invoices-element" }
) do |table|
  table.caption("Invoice history")
  table.thead do
    table.tr do
      table.th("Invoice")
      table.th("Issued")
      table.th("Status")
      table.th("Amount", align: :right)
      table.th("Action", align: :right)
    end
  end
  table.tbody do
    Gallery::Data.invoices.each do |invoice|
      table.tr do
        table.th(invoice.number, scope: :row)
        table.td(invoice.issued_on.iso8601)
        table.td do
          render NitroKit::Badge.new(
            invoice.status.to_s.humanize,
            id: "gallery-table-invoice-#{invoice.id}-status",
            color: invoice_status_color(invoice.status),
            size: :sm
          )
        end
        table.td(format_amount(invoice), align: :right)
        table.td(align: :right) do
          render NitroKit::Button.new(
            "View",
            id: "gallery-table-invoice-#{invoice.id}-view",
            href: "#invoice-#{invoice.id}",
            size: :sm
          )
        end
      end
    end
  end
end

Alignment

Headers and cells declare left, center, and right alignment independently.

All alignments

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-alignment",
  table_html: { id: "gallery-table-alignment-element" }
) do |table|
  table.caption("Deployment timing")
  table.thead do
    table.tr do
      table.th("Environment", align: :left)
      table.th("State", align: :center)
      table.th("Duration", align: :right)
    end
  end
  table.tbody do
    table.tr do
      table.th("Production", scope: :row, align: :left)
      table.td("Operational", align: :center)
      table.td("2m 14s", align: :right)
    end
    table.tr do
      table.th("Staging", scope: :row, align: :left)
      table.td("Queued", align: :center)
      table.td("", align: :right)
    end
  end
end

Content pressure

Long identifiers, multiline descriptions, dense records, status, and grouped actions stay semantic.

API credential inventory

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-credentials",
  table_html: { id: "gallery-table-credentials-element" }
) do |table|
  table.caption("API credential inventory")
  table.thead do
    table.tr do
      table.th("Credential and identifier")
      table.th("Access", align: :center)
      table.th("Last used")
      table.th("Actions", align: :right)
    end
  end
  table.tbody do
    Gallery::Data.api_keys.each do |api_key|
      table.tr do
        table.th(scope: :row) do
          strong { api_key.name }
          div { "#{api_key.prefix}••••••••••••••••" }
        end
        table.td(align: :center) do
          render NitroKit::Badge.new(
            api_key.access.to_s.humanize,
            id: "gallery-table-credential-#{api_key.id}-access",
            variant: :outline,
            color: :info,
            size: :sm
          )
        end
        table.td(api_key.last_used_at&.iso8601 || "Never used")
        table.td(align: :right) do
          render NitroKit::ButtonGroup.new(
            id: "gallery-table-credential-#{api_key.id}-actions",
            label: "Actions for #{api_key.name} credential"
          ) do |group|
            group.button(
              "Rotate",
              id: "gallery-table-credential-#{api_key.id}-rotate",
              size: :sm,
            )
            group.button(
              "Revoke",
              id: "gallery-table-credential-#{api_key.id}-revoke",
              size: :sm,
              variant: :destructive
            )
          end
        end
      end
    end
  end
end

Integration descriptions

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-integrations",
  table_html: { id: "gallery-table-integrations-element" }
) do |table|
  table.caption("Integration delivery and connection status")
  table.thead do
    table.tr do
      table.th("Integration")
      table.th("Purpose and delivery behavior")
      table.th("Status", align: :right)
    end
  end
  table.tbody do
    Gallery::Data.integrations.each do |integration|
      table.tr do
        table.th(integration.name, scope: :row)
        table.td(integration.description)
        table.td(align: :right) do
          render NitroKit::Badge.new(
            integration.status.to_s.humanize,
            id: "gallery-table-integration-#{integration.id}-status",
            color: integration_status_color(integration.status),
            size: :sm
          )
        end
      end
    end
  end
end

Empty state

An empty collection remains a valid table with a caption, headers, and one spanning message.

No API keys

ViewportFull width
Rubytest/dummy/app/components/gallery/components/table_page.rb
render NitroKit::Table.new(
  id: "gallery-table-empty",
  table_html: { id: "gallery-table-empty-element" }
) do |table|
  table.caption("API credentials")
  table.thead do
    table.tr do
      table.th("Name")
      table.th("Access")
      table.th("Last used", align: :right)
    end
  end
  table.tbody do
    table.tr do
      table.td(
        "No API credentials have been created.",
        html: { colspan: 3 }
      )
    end
  end
end