# Invite Modal

> A complete screen built from AppShell, Avatar, Badge, Button and 8 more components.

Pattern · Pro kit · [Live page](https://ui.twentyfirst.media/pro/patterns/InviteModalScreen/)

## Code

```tsx
import * as React from "react";
import {
  AppShell,
  Avatar,
  Badge,
  Button,
  Card,
  Input,
  Modal,
  PageHeader,
  Select,
  StatTile,
  Toast,
} from "@twentyfirstmedia/pro";

function App() {
  return (
    <div
      style={{
        position: "relative",
      }}
    >
      <AppShell
        brand="Halo"
        tag="Admin"
        user={{ name: "Mara Ellis", email: "mara@halo.app" }}
        active="People"
        dock={false}
      >
        <PageHeader
          title="People"
          subtitle="Everyone with access to this workspace"
          actions={[
            <Button key="b" icon="users">
              Invite
            </Button>,
          ]}
        />
        <div
          className="tf-grid"
          style={
            {
              "--cols": "repeat(4, 1fr)",
              "--cols-sm": "repeat(2, minmax(0, 1fr))",
              "--cols-md": "repeat(2, minmax(0, 1fr))",
            } as React.CSSProperties
          }
        >
          <StatTile tint="sky" value="38" label="Members" />
          <StatTile tint="mint" value="31" label="Active today" />
          <StatTile tint="placeholder" value="3" label="Pending invites" />
          <StatTile tint="lilac" value="6" label="Admins" />
        </div>
        <div
          className="tf-grid"
          style={
            {
              "--cols": "repeat(3, 1fr)",
            } as React.CSSProperties
          }
        >
          {["Mara Ellis", "Jonah Petit", "Sun Lee", "Rui Costa", "Ida Nolan", "Ava Brandt"].map(
            (n) => (
              <Card
                key={n}
                title={n}
                description="Member"
                corner={<Avatar name={n} size={32} />}
                status={<Badge tone="success">Active</Badge>}
              />
            ),
          )}
        </div>
      </AppShell>
      <div
        style={{
          position: "absolute",
          inset: 0,
          borderRadius: "var(--radius-xl)",
          overflow: "hidden",
        }}
      >
        <Modal
          style={{
            position: "absolute",
            inset: 0,
          }}
          icon="users"
          tint="sky"
          title="Invite teammates"
          text="They get access to every project in this workspace."
          actions={[
            <Button key={1} variant="ghost">
              Cancel
            </Button>,
            <Button key={2} icon="send">
              Send 3 invites
            </Button>,
          ]}
        >
          <Input
            label="Email addresses"
            defaultValue="leo@bluefern.io, nina@bluefern.io, ops@bluefern.io"
          />
          <Select label="Role" options={["Editor", "Viewer", "Admin"]} />
          <div className="tf-row">
            <Badge tone="info">3 seats left after this</Badge>
            <Badge>Expires in 7 days</Badge>
          </div>
        </Modal>
      </div>
      <div
        style={{
          position: "absolute",
          right: 40,
          bottom: 40,
        }}
      >
        <Toast action="Undo">Ava Brandt was removed</Toast>
      </div>
    </div>
  );
}
export default function Preview() {
  return <App />;
}
```

## Props

### AppShell

| Prop | Type | Notes |
| --- | --- | --- |
| `active?` | `string` |  |
| `brand?` | `ReactNode` | The product name, or a node (a logo mark with the name; collapsed sidebars show it whole). |
| `tag?` | `string` |  |
| `nav?` | `NavItem[]` |  |
| `user?` | `{ name: string; email?: string }` |  |
| `onNavigate?` | `(label: string) => void` |  |
| `topExtra?` | `ReactNode` |  |
| `sheetStyle?` | `CSSProperties` |  |
| `contentKey?` | `string \| number` |  |
| `side?` | `ReactNode` |  |
| `dock?` | `boolean` |  |
| `dockItems?` | `DockProps["items"]` |  |
| `railTop?` | `RailItem[]` |  |
| `railBottom?` | `RailItem[]` |  |
| `navigation?` | `"top" \| "sidebar"` | `top` (default): navigation in the top bar, tools in the side rail. `sidebar`: a labelled sidebar on the frame, with groups, nesting and a drawer on phones. |
| `sidebarGroups?` | `SidebarGroup[]` | With `navigation="sidebar"`: groups instead of the flat `nav` list. |
| `sidebar?` | `Partial<Omit<SidebarProps, "variant" \| "groups">>` | With `navigation="sidebar"`: more sidebar props (collapsed, storageKey, shortcut, linkAs …). |
| `workspaces?` | `WorkspaceSwitcherProps` | With `navigation="sidebar"`: the workspace switcher at the top instead of the brand. |
| `userMenu?` | `(MenuItem \| "-")[]` | The account menu's entries (with `navigation="sidebar"` at the foot of the sidebar). |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Avatar

| Prop | Type | Notes |
| --- | --- | --- |
| `name` | `string` | Required |
| `src?` | `string` |  |
| `size?` | `number` |  |
| `tint?` | `Tint \| "surface-sunken"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Badge

| Prop | Type | Notes |
| --- | --- | --- |
| `tone?` | `Tone` |  |
| `dot?` | `boolean` |  |
| `live?` | `boolean` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Button

| Prop | Type | Notes |
| --- | --- | --- |
| `variant?` | `"primary" \| "secondary" \| "soft" \| "ghost" \| "danger" \| "danger-solid"` |  |
| `size?` | `"md" \| "sm"` |  |
| `icon?` | `IconName` |  |
| `iconRight?` | `IconName` |  |
| `disabled?` | `boolean` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `children?` | `ReactNode` |  |
| `asChild?` | `boolean` | Render the single child element (a link, a router Link) with the button's look instead of a <button>. |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Card

| Prop | Type | Notes |
| --- | --- | --- |
| `title?` | `string` |  |
| `description?` | `string` |  |
| `meta?` | `string[]` |  |
| `corner?` | `ReactNode` |  |
| `status?` | `ReactNode` |  |
| `actions?` | `ReactNode[]` |  |
| `tint?` | `Tint \| "surface"` |  |
| `onClick?` | `() => void` |  |
| `children?` | `ReactNode` |  |
| `eyebrow?` | `ReactNode` |  |
| `surface?` | `Surface` | `"glass"`: translucent glass over what is behind (maps, 3D, video, photos); `"solid"` opts out inside a glass `UIProvider`. |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### IconButton

| Prop | Type | Notes |
| --- | --- | --- |
| `icon` | `IconName` | Required |
| `label` | `string` | Accessible name and tooltip (`aria-label` and `title`); `aria-label` or `title` override them. |
| `variant?` | `"soft" \| "solid" \| "outline" \| "ghost"` |  |
| `size?` | `"sm" \| "md" \| "lg"` |  |
| `type?` | `"button" \| "submit" \| "reset"` |  |
| `onClick?` | `(e: MouseEvent<HTMLButtonElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `asChild?` | `boolean` | Render the single child element (a link) with the button's look; the icon is placed inside it. |
| `children?` | `ReactNode` |  |
| `id?` | `string` |  |
| `name?` | `string` | Form field name and value, sent with the form when this button submits it. |
| `value?` | `string` |  |
| `form?` | `string` | The id of the form this button belongs to, when it sits outside it. |
| `title?` | `string` |  |
| `tabIndex?` | `number` |  |
| `autoFocus?` | `boolean` |  |
| `aria-label?` | `string` |  |
| `aria-labelledby?` | `string` |  |
| `aria-describedby?` | `string` |  |
| `aria-expanded?` | `boolean \| "true" \| "false"` | For disclosure and menu triggers: whether what the button controls is open. |
| `aria-controls?` | `string` |  |
| `aria-haspopup?` | `boolean \| "true" \| "false" \| "menu" \| "listbox" \| "tree" \| "grid" \| "dialog"` |  |
| `aria-pressed?` | `boolean \| "true" \| "false" \| "mixed"` | For toggle buttons. |
| `aria-current?` | `boolean \| "true" \| "false" \| "page" \| "step" \| "location" \| "date" \| "time"` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Input

| Prop | Type | Notes |
| --- | --- | --- |
| `type?` | `string` |  |
| `placeholder?` | `string` |  |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `disabled?` | `boolean` |  |
| `onChange?` | `(e: ChangeEvent<HTMLInputElement>) => void` |  |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Modal

| Prop | Type | Notes |
| --- | --- | --- |
| `title` | `string` | Required |
| `text?` | `string` |  |
| `icon?` | `IconName` |  |
| `tint?` | `Tint` |  |
| `actions?` | `ReactNode[]` |  |
| `onClose?` | `() => void` |  |
| `style?` | `CSSProperties` |  |
| `children?` | `ReactNode` |  |
| `open?` | `boolean` | Optional: with `open` the component stays mounted while it animates out; without it, render it only while open. |
| `className?` | `string` | Extra class on the root element. |

### PageHeader

| Prop | Type | Notes |
| --- | --- | --- |
| `crumbs?` | `(string \| Crumb)[]` | A breadcrumb above the title (see `Breadcrumb`): labels, or items with `href`/`onClick`. |
| `crumbLinkAs?` | `ElementType` | The element crumb links render as, e.g. Next.js `Link`. |
| `title` | `string` | Required |
| `subtitle?` | `string` |  |
| `actions?` | `ReactNode[]` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Select

| Prop | Type | Notes |
| --- | --- | --- |
| `options` | `string[]` | Required |
| `value?` | `string` |  |
| `defaultValue?` | `string` |  |
| `onChange?` | `(e: ChangeEvent<HTMLSelectElement>) => void` |  |
| `label?` | `string` |  |
| `hint?` | `string` |  |
| `error?` | `string` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### StatTile

| Prop | Type | Notes |
| --- | --- | --- |
| `value` | `ReactNode` | Required |
| `label` | `string` | Required |
| `tint?` | `Tint \| "surface" \| "placeholder"` |  |
| `delta?` | `string` |  |
| `delay?` | `number` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |

### Toast

| Prop | Type | Notes |
| --- | --- | --- |
| `icon?` | `IconName` |  |
| `tint?` | `Tint` |  |
| `action?` | `string` |  |
| `onAction?` | `() => void` |  |
| `children?` | `ReactNode` |  |
| `className?` | `string` | Extra class on the root element. |
| `style?` | `CSSProperties` | Inline style on the root element. |
