Workspace members
A source-copy member list with consumer-owned identities, role and status labels, and native actions.
Show the people in a workspaceLink to section
WorkspaceMembersPanel composes a named section, member list, readable role and
status labels, and optional actions. Your application supplies every member and
chooses which controls to show. The Block does not manage identity or access.
Fictional local demonstration
Names and addresses below are examples. The controls only change local demo state or select a sample member. They do not send invitations or email, change permissions, call a backend, or save data. The UI setup link is documentation navigation.
Fictional local example. No invitations, permission changes, requests or emails are sent. Nothing is saved. Actions only select a sample member.
Example workspace members
Names, roles, statuses and actions are supplied by this demo, not inferred by the Block.
Alex Morgan
alex@example.com
- Role
- Workspace owner
- Status
- Active example
Jordan Lee
- Role
- Project observer
- Status
- Awaiting review example
Casey Park — a deliberately long fictional member name for a narrow workspace
casey.long.example.address@example.com
- Role
- External collaborator with a deliberately long role label
- Status
- Unavailable example action
No example member selected.
Canonical identityLink to section
- Category: Account & workspace, slug
account. - Block:
workspace-members, exportWorkspaceMembersPanel. - Canonical source:
apps/marketing/content/blocks/account/workspace-members/. - Complete entry point:
index.tsx.
The canonical Block uses React and the public UI package only. It does not require Next.js, an application alias, another copied Block or a service. There is no Blocks npm package, Registry installer or CLI in this flow.
Copy sourceLink to section
Configure the public UI package and tokens, then create
src/components/blocks/workspace-members/index.tsx in your application.
Open the source below and copy the complete file, preserving its public imports.
Use the code block's copy button or select the code manually when clipboard
access is unavailable. The displayed source is included at documentation build
time; it is not a second implementation or a copy of the interactive demo.
View and copy the complete source
import * as React from "react";
import { Badge, EmptyState, cn } from "@pycolors/ui";
export interface WorkspaceMember {
/** Stable identity, unique within this panel; used as the React key. */
id: string;
name: string;
secondaryText?: string;
/** Display text only. The application owns authorization. */
role: string;
status: string;
actions?: React.ReactNode;
}
export interface WorkspaceMembersPanelProps {
/** Stable HTML id, unique across the consuming page. */
id: string;
heading: string;
description?: string;
members: readonly WorkspaceMember[];
actions?: React.ReactNode;
emptyTitle?: string;
emptyDescription?: string;
className?: string;
}
function MemberActions({ children }: { children?: React.ReactNode }) {
if (!React.Children.toArray(children).some((child) => child !== "")) {
return null;
}
return (
<div
data-slot="workspace-members-actions"
className="flex min-w-0 flex-wrap items-center gap-2 [&>*]:max-w-full [&>*]:whitespace-normal [&>*]:break-words"
>
{children}
</div>
);
}
export function WorkspaceMembersPanel({
id,
heading,
description,
members,
actions,
emptyTitle = "No members to display",
emptyDescription,
className,
}: WorkspaceMembersPanelProps) {
const headingId = `${id}-heading`;
const descriptionId = description ? `${id}-description` : undefined;
return (
<section
id={id}
aria-labelledby={headingId}
aria-describedby={descriptionId}
data-slot="workspace-members-panel"
className={cn(
"min-w-0 rounded-xl border border-border bg-card text-card-foreground",
className,
)}
>
<div className="flex min-w-0 flex-col gap-4 border-b border-border p-4 sm:flex-row sm:items-start sm:justify-between sm:p-6">
<div className="min-w-0 space-y-1">
<h2 id={headingId} className="break-words text-base font-semibold">
{heading}
</h2>
{description ? (
<p
id={descriptionId}
className="break-words text-sm text-muted-foreground"
>
{description}
</p>
) : null}
</div>
<MemberActions>{actions}</MemberActions>
</div>
{members.length > 0 ? (
<ul
role="list"
aria-labelledby={headingId}
className="divide-y divide-border"
>
{members.map((member) => (
<li
key={member.id}
data-slot="workspace-member"
className="flex min-w-0 flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between sm:p-6"
>
<div className="min-w-0 flex-1 space-y-3">
<div className="min-w-0 space-y-1">
<h3 className="break-words text-sm font-medium">
{member.name}
</h3>
{member.secondaryText ? (
<p className="break-words text-sm text-muted-foreground">
{member.secondaryText}
</p>
) : null}
</div>
<dl className="flex min-w-0 flex-wrap gap-x-6 gap-y-2">
<div className="min-w-0 space-y-1">
<dt className="text-xs text-muted-foreground">Role</dt>
<dd className="min-w-0 break-words text-sm">
{member.role}
</dd>
</div>
<div className="min-w-0 space-y-1">
<dt className="text-xs text-muted-foreground">Status</dt>
<dd className="min-w-0">
<Badge
variant="outline"
className="h-auto max-w-full whitespace-normal break-words"
>
{member.status}
</Badge>
</dd>
</div>
</dl>
</div>
<MemberActions>{member.actions}</MemberActions>
</li>
))}
</ul>
) : (
<EmptyState
title={emptyTitle}
description={emptyDescription}
ariaLive="off"
className="min-w-0 break-words rounded-none border-0 p-6 sm:p-8"
/>
)}
</section>
);
}
Install by copying sourceLink to section
Keep the complete file at the application-owned path above. Install and configure
your compatible public @pycolors/ui and @pycolors/tokens dependencies first;
copying source does not install packages or CSS. Replace the example content and
routes with your own, then validate it in your application.
import { Button } from "@pycolors/ui";
import { WorkspaceMembersPanel } from "./blocks/workspace-members";
export function WorkspacePeople() {
return (
<WorkspaceMembersPanel
id="workspace-people"
heading="Workspace members"
description="People associated with this workspace."
members={[
{
id: "example-alex",
name: "Alex Morgan",
secondaryText: "alex@example.com",
role: "Project observer",
status: "Active",
actions: (
<Button asChild variant="outline">
<a href="/workspace/members/example-alex">View Alex Morgan</a>
</Button>
),
},
]}
/>
);
}The example route belongs to your application, not PyColors. The canonical file
needs no "use client" directive or hooks. When supplying ordinary event
handlers, define them inside an appropriate consumer client boundary; do not
pass handlers from a Server Component across that boundary. The interactive
documentation example owns its local state and client boundary separately.
Content and actions remain yoursLink to section
id, heading and members are required. Use a stable, nonempty id unique
across the page, including its derived -heading and optional -description
IDs. Each member needs a stable id unique within the list. Members in separate
panels can share member IDs because these are React keys, not HTML IDs.
The section uses an h2; member names and the empty-state title use h3.
Place it below your page heading. Optional description text names the section's
purpose. Member secondaryText is optional plain text, not an implicit email
link. An absent secondary value renders no empty paragraph.
Member role and status are display strings. They do not authorize a user,
choose actions, or map to access rights. Status uses a neutral outlined Badge
with visible text rather than an inferred color or unsolicited live region.
The Block preserves member order and values and does not sort or filter them.
Supply optional panel actions and per-member actions as React nodes. Nodes
are rendered unchanged, preserving refs, callbacks, native destinations, disabled
state and button types. Use distinct accessible names such as View Alex Morgan
when several members have similar controls. Set type="button" for controls
that must not submit a surrounding form. Disabled presentation is not a security
boundary: enforce permissions in your application's validated backend.
Missing, null, false, empty-string and empty-array action slots omit the action group. Opaque components that render nothing remain consumer-owned content. Omit all actions when there is no useful next step; do not invent disabled permissions or expose a destructive action solely to fill the layout.
Empty, disabled and long-content casesLink to section
Pass members={[]} for a successful empty result. The public EmptyState
primitive renders emptyTitle (default No members to display) and optional
emptyDescription, with announcements off. Panel actions remain available if
your application supplied them. Loading and request failure are not empty states;
render your own appropriate loading/error UI outside this presentation Block.
The preview's state selector demonstrates both populated and empty lists. Its action toggle removes section and member actions; the final sample row has an explicitly disabled local control and deliberately long text. These are demo choices, not inferred permissions or production membership states.
The layout stacks on small screens and arranges content and actions horizontally
from sm, while labels and actions wrap. Semantic tokens support both themes.
Use className for root overrides through public cn; consumer classes take
precedence. Review real long names, role/status text and controls at narrow
widths, especially controls with their own fixed dimensions.
Focus, validation and maintenanceLink to section
The Block never moves focus, intercepts keys, fetches data or changes members. Native controls keep their behavior. Your application owns focus after removing an active control or replacing content. The demo keeps the clicked button in place and reports local selection through a separate polite status message.
Run your application's lint, type-check, tests and production build after copying. Review populated/empty lists, no actions, disabled controls, rerenders, multiple panels, long content, keyboard focus, both themes and narrow layouts. Runtime tests exercise the actual Block and demo; representative axe checks do not certify visual contrast, screen-reader behavior or complete accessibility.
Your application owns the copied source and does not receive automatic updates. Review later canonical changes deliberately, preserve local customizations and validate it in your application before adoption. Revert your local copy to roll back presentation without changing member data.
Boundaries and next stepsLink to section
No invitations, role changes, removals, authentication, authorization, billing, email, analytics, persistence or production services are implemented here. The separate Data table owns tabular pagination and row composition; this Block is a focused member list, not another data engine.
Explore the Blocks catalog for other compositions, or compare Starters for an application foundation.