Senren UI
Components / Team Member List

Team Member List

Stable

team management pages

  • MN

    Mai Nguyen

    Owner

    Active
  • AT

    An Tran

    Designer

    Invited
  • LP

    Linh Pham

    linh@example.com

    Active

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/.../team_member_list_example.html.erb
<div class="w-full max-w-md">
  <%= render Senren::TeamMemberListComponent.new(
    members: [
      { initials: "MN", name: "Mai Nguyen", role: "Owner", status: "Active" },
      { initials: "AT", name: "An Tran", role: "Designer", status: "Invited" },
      { initials: "LP", name: "Linh Pham", email: "linh@example.com", status: "Active" }
    ]
  ) %>
</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 team_member_list

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 team_member_list --no-client

Dependencies are resolved by senren:add: avatar, badge, button, dropdown_menu.

At a glance #

Category Saas
Class name Senren::TeamMemberListComponent
Stimulus
Variants default
Depends on avatar, badge, button, dropdown_menu
Pairs with invite_member_dialog, search_input

Source #

app/components/senren/team_member_list_component.html.erb
<%= tag.div(**root_attrs("rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-card))] shadow-sm")) do %>
  <% if content? && members.empty? %>
    <div class="p-4"><%= content %></div>
  <% else %>
    <ul class="divide-y divide-[hsl(var(--senren-border))]">
      <% members.each do |member| %>
        <li class="flex items-center justify-between gap-4 p-4">
          <div class="flex min-w-0 items-center gap-3">
            <span class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[hsl(var(--senren-muted))] text-sm font-semibold text-[hsl(var(--senren-muted-foreground))]"><%= initials_for(member) %></span>
            <div class="min-w-0">
              <p class="truncate text-sm font-medium text-[hsl(var(--senren-foreground))]"><%= member_value(member, :name) %></p>
              <p class="truncate text-xs text-[hsl(var(--senren-muted-foreground))]"><%= member_value(member, :role) || member_value(member, :email) %></p>
            </div>
          </div>
          <% if member_value(member, :status).present? %>
            <span class="rounded-full bg-[hsl(var(--senren-muted))] px-2 py-1 text-xs font-medium text-[hsl(var(--senren-muted-foreground))]"><%= member_value(member, :status) %></span>
          <% end %>
        </li>
      <% end %>
    </ul>
  <% end %>
<% end %>
app/components/senren/team_member_list_component.rb
# frozen_string_literal: true

module Senren
  class TeamMemberListComponent < BaseComponent
    VARIANTS = { default: '' }.freeze
    SIZES = { md: '' }.freeze

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

    attr_reader :members

    def member_value(member, key)
      member.is_a?(Hash) ? (member[key] || member[key.to_s]) : nil
    end

    def initials_for(member)
      explicit = member_value(member, :initials)
      return explicit if explicit.present?

      member_value(member, :name).to_s.split.map { |part| part[0] }.join.first(2).upcase
    end
  end
end

AI agent rules #

Use for

  • +team management pages

Avoid

  • -arbitrary user lists - compose with table or card

Accessibility #

  • Use semantic list markup.