# Access List

> Manage who has access and what they can do.

- Name: `access-list`
- Status: beta
- Categories: people, forms, display
- File: the install writes `access-list.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `lucide-react`. The install adds them.
- Other registry items: `@arbokui/motion`, `@arbokui/button`, `@arbokui/menu`, `@arbokui/person`, `@arbokui/tool-button`, `@arbokui/tooltip`. The install adds them.
- Gallery: https://arbokui.com/components/access-list
- Registry item: https://arbokui.com/r/access-list.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/access-list
   ```

## Use it for

- The people part of a share sheet: who can open a page, a plan, or a file, and whether they view, comment, or edit.
- Each entry is a person, an email with no account, a phone number, or the link. Give each an id.
- Each row's level is a menu: Can view, Can comment, Can edit, then Remove access. A change applies at once, and ⌘Z takes it back for 8 seconds.
- Remove access shows Removed with Undo on the row. onRemove runs once the window ends.
- Set asked on an entry to show a request. Requests come first, with Approve and Decline in the row.
- Guests view or comment. Set guestsCanEdit when the owner lets them edit. The link and a phone number only view.
- Put it in a Card with className="gap-0 py-0". Its rows bring their own padding and lines.

## Avoid it for

- Picking people to share with. Use a token field or a search above the list.
- Roles in a team, like admin or member. This is about access to one thing.
- Setting manage on someone who cannot change access. Pass manage={false} and the levels are plain words.

## Props

- `entries`: `AccessEntry[]`. Required. { id, person?, photo?, email?, phone?, link?, meta?, guest?, owner?, level?, asked? }. level and asked are "view" | "comment" | "edit".
- `guestsCanEdit`: `boolean`. Default `false`. The owner lets guests edit. Off, guests view or comment.
- `manage`: `boolean`. Default `true`. You can change access. Off, levels are plain words and requests have no buttons.
- `onLevel`: `(id: string, level: "view" | "comment" | "edit") => void`. A level changed, at once. It runs again with the old level if the change is undone.
- `onRemove`: `(id: string) => void`. Removed, once the 8-second undo window ends.
- `onApprove`: `(id: string, level: "view" | "comment" | "edit") => void`. A request approved, once the undo window ends, with the level given.
- `onDecline`: `(id: string) => void`. A request declined, once the undo window ends.
- `className`: `string`. Extra classes on the list. Later classes win.

## Accessibility

- A list of rows. Each level is a menu button named by the person and the level, like "Kate Clarke can edit".
- In the menu, the arrow keys move without wrapping, Enter chooses, and Esc closes it and puts focus back on the button. The current level is a checked menuitemradio.
- After Remove access, the same button says "Kate Clarke removed" and is the Undo, armed 1.2 seconds after a key press. It no longer says it opens a menu.
- Decline is an icon named "Decline" plus the name, and its tooltip says Decline. Approve's name carries the name too.
- Each result is said once in the page's polite live region. ⌘Z or Ctrl+Z takes back the newest change, outside a text field.

## Examples

### In a share sheet

```tsx
"use client";
import { useState } from "react";
import { AccessList, type AccessEntry } from "@/components/ui/access-list";
import { Card } from "@/components/ui/card";

export function Access() {
  const [entries, setEntries] = useState<AccessEntry[]>([
    { id: "sam", person: "Sam Carter", meta: "Design lead", owner: true, level: "edit" },
    { id: "rob", person: "Rob Kerr", meta: "Client, guest", guest: true, asked: "comment" },
    { id: "kate", person: "Kate Clarke", level: "edit" },
    { id: "link", link: true, meta: "View only · 3 visitors", level: "view" },
  ]);
  const set = (id: string, change: Partial<AccessEntry>) =>
    setEntries((all) => all.map((entry) => (entry.id === id ? { ...entry, ...change } : entry)));
  const drop = (id: string) => setEntries((all) => all.filter((entry) => entry.id !== id));
  return (
    <Card className="gap-0 py-0">
      <AccessList
        entries={entries}
        onLevel={(id, level) => set(id, { level })}
        onRemove={drop}
        onApprove={(id, level) => set(id, { asked: undefined, level })}
        onDecline={drop}
      />
    </Card>
  );
}
```
