Invite Member Dialog
Stable Stimulus:senren--invite-member-dialog
team invitation flow
Invite teammate
Send a workspace invite with the right role.
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/.../invite_member_dialog_example.html.erb
<div class="w-full max-w-md">
<%= render Senren::InviteMemberDialogComponent.new(
title: "Invite teammate",
description: "Send a workspace invite with the right role.",
roles: ["Member", "Admin", "Billing"]
) do |dialog| %>
<% dialog.with_trigger do %>
<%= render(Senren::ButtonComponent.new(variant: :primary)) { "Invite teammate" } %>
<% end %>
<% end %>
</div>
Install this component #
Copy the official component into your app
This component requires Stimulus. Keep --client so the controller is copied with the ViewComponent.
Terminal
bin/rails senren:add invite_member_dialog --client
Create a custom component with the same conventions
Use this when you need an app-specific component that follows Senren's ViewComponent and Stimulus structure. --client is required for this behavior.
Terminal
bin/rails generate senren:component invite_member_dialog --client
Dependencies are resolved by senren:add:
dialog, form, input, button.
At a glance #
Category
Saas
Class name
Senren::InviteMemberDialogComponent
Stimulus
senren--invite-member-dialog
Variants
default
Depends on
dialog, form, input, button
Pairs with
team_member_list
Source #
app/components/senren/invite_member_dialog_component.html.erb
<%= tag.div(**root_attrs("inline-block", data: { controller: "senren--invite-member-dialog" })) do %>
<% if trigger? %>
<span data-action="click->senren--invite-member-dialog#open" data-senren--invite-member-dialog-target="trigger"><%= trigger %></span>
<% else %>
<button type="button" class="inline-flex h-10 cursor-pointer items-center justify-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-4 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90" data-action="click->senren--invite-member-dialog#open" data-senren--invite-member-dialog-target="trigger">
<%= button_label %>
</button>
<% end %>
<div hidden data-senren--invite-member-dialog-target="overlay" class="fixed inset-0 z-40 bg-[hsl(var(--senren-foreground)/0.42)] backdrop-blur-sm"></div>
<div hidden id="<%= dom_id %>" data-senren--invite-member-dialog-target="panel" role="dialog" aria-modal="true" aria-labelledby="<%= dom_id %>-title" tabindex="-1" class="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] p-6 text-[hsl(var(--senren-popover-foreground))] shadow-lg">
<h2 id="<%= dom_id %>-title" class="font-display text-lg font-semibold"><%= title %></h2>
<% if description.present? %>
<p class="mt-1.5 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]"><%= description %></p>
<% end %>
<div class="mt-5 space-y-4">
<% if content? %>
<%= content %>
<% else %>
<label class="block text-sm font-medium text-[hsl(var(--senren-foreground))]" for="<%= dom_id %>-email">Email</label>
<input id="<%= dom_id %>-email" name="<%= email_name %>" type="email" placeholder="teammate@company.com" class="h-10 w-full rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-3 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
<label class="block text-sm font-medium text-[hsl(var(--senren-foreground))]" for="<%= dom_id %>-role">Role</label>
<select id="<%= dom_id %>-role" name="<%= role_name %>" class="h-10 w-full rounded-(--senren-radius) border border-[hsl(var(--senren-input))] bg-[hsl(var(--senren-background))] px-3 text-sm outline-none focus:ring-2 focus:ring-[hsl(var(--senren-ring))]">
<% roles.each do |role| %><option><%= role %></option><% end %>
</select>
<% end %>
</div>
<div class="mt-6 flex justify-end gap-2">
<button type="button" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) px-4 text-sm font-medium hover:bg-[hsl(var(--senren-accent))]" data-action="click->senren--invite-member-dialog#close">Cancel</button>
<% if footer? %><%= footer %><% else %><button type="submit" class="inline-flex h-10 cursor-pointer items-center rounded-(--senren-radius) bg-[hsl(var(--senren-primary))] px-4 text-sm font-medium text-[hsl(var(--senren-primary-foreground))] hover:opacity-90">Send invite</button><% end %>
</div>
</div>
<% end %>
app/components/senren/invite_member_dialog_component.rb
# frozen_string_literal: true
module Senren
class InviteMemberDialogComponent < BaseComponent
renders_one :trigger
renders_one :footer
VARIANTS = { default: '' }.freeze
SIZES = { md: '' }.freeze
def initialize(title: 'Invite teammate', description: 'Send an invitation to join this workspace.',
email_name: 'email', role_name: 'role', roles: %w[Member
Admin], button_label: 'Invite member', id: nil, class_name: nil, **html)
super(variant: :default, size: :md, class_name: class_name, **html)
@title = title
@description = description
@email_name = email_name
@role_name = role_name
@roles = Array(roles)
@button_label = button_label
@dom_id = id || "senren-invite-member-#{SecureRandom.hex(3)}"
end
attr_reader :title, :description, :email_name, :role_name, :roles, :button_label, :dom_id
end
end
AI agent rules #
Use for
- +team invitation flow
Avoid
- -bulk imports - use a dedicated screen
Accessibility #
- Inherit dialog accessibility; validate email server-side.