# Domain access

List members, edit roles, manage domain access and pending invitations.

Import: @slices.sh/auth/headless

Public parts: 11

## Installation

### 1. Install the service registry

services/index.ts, which `import "@/services"` loads. A provider setup writes the same file; skip this if your app has one.

```bash
npx shadcn@latest add "https://slices.sh/r/services.json"
```

### 2. Install the headless library

```bash
npm install @slices.sh/auth@^0.1.10
```

### 3. Install the service

Install the provider configuration via shadcn or the slices CLI. Skip this step if your app already has the service configured. Provider commands and provisioning: https://slices.sh/docs/services/auth.md

## Anatomy

### Composition

```text
User.Root
  Organization.Active
    Members.AccessScope
      Members.DomainAccess
        Members.AddDomain
          Members.DomainInput
          Members.DomainSummary
          Members.DomainRole
          Members.DomainSubmit
          Members.DomainError
        Members.Domains
          Members.RemoveDomain
  Members.NoDomainAccess
```

## Minimal composition

```tsx
"use client"

import "@/services"
import { User, Members } from "@slices.sh/auth/headless"

export function Example() {
  return (
    <User.Root>
      <ul>
        <Members.Item>
          <Members.Name />
          <Members.Email />
        </Members.Item>
      </ul>
      <Members.Empty>No members.</Members.Empty>
    </User.Root>
  )
}

```

Requires an authenticated account. Organization controls also require organizations and the appropriate role.

## Complete API reference

### Members.AccessScope

scope

An organization/role change cannot leave a now-hidden access tab selected.

Import: @slices.sh/auth/headless

```tsx
RootProps
```

Alternate import: Members.AccessScope from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/access-scope.tsx

### Members.DomainAccess

gate

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps
```

Alternate import: Members.DomainAccess from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-access.tsx

### Members.NoDomainAccess

gate

Why nobody can claim a domain here yet.

The mirror of `Members.DomainAccess`, and not a courtesy: claiming a domain has four
conditions and meeting none of them looks exactly like meeting three, so a screen that
only drew the form drew nothing and read as a feature that does not work. Each reason is
a different next step — sign in, verify the address, make an organization, ask an owner —
and words for them belong to the pattern. → docs/rules/capability-honesty.md

Closed while the session and the organization are still loading, so a page does not
accuse somebody of being signed out for the half-second before it knows.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps<{ blocked: DomainBlock }>
```

Alternate import: Members.NoDomainAccess from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/no-domain-access.tsx

### Members.DomainError

content · Element: p

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"p", { message: string }>
```

Alternate import: Members.DomainError from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-error.tsx

### Members.DomainRole

input · Element: select

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

Members.DomainRole must be inside <Members.AddDomain>.

```tsx
PartProps<"select", { roles: readonly ("member" | "admin")[] }>
```

Alternate import: Members.DomainRole from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-role.tsx

### Members.RemoveDomain

action · Element: button

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

Members.RemoveDomain must be inside <Members.Domains>.

```tsx
PartProps<"button">
```

Alternate import: Members.RemoveDomain from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/remove-domain.tsx

### Members.DomainSubmit

action · Element: button

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

Members.DomainSubmit must be inside <Members.AddDomain>.

```tsx
PartProps<"button", { pending: boolean }>
```

Alternate import: Members.DomainSubmit from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-submit.tsx

### Members.DomainInput

input · Element: input

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

Members.DomainInput must be inside <Members.AddDomain>.

```tsx
PartProps<"input", { invalid: boolean }>
```

Alternate import: Members.DomainInput from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-input.tsx

### Members.DomainSummary

content · Element: p

What allowing this domain will do, in the words of the domain in the field.

A form headed "Invite by domain" said nothing about what the domain was for, and the
only sentence anywhere near it was the one refusing a free provider's — which is how a
screen ends up explaining that gmail.com is nobody's before saying what a domain does.
The sentence follows what is typed, so it is the field's own answer rather than a note.

Import: @slices.sh/auth/headless

Capability: organizations

Members.DomainSummary must be inside <Members.AddDomain>.

```tsx
PartProps<"p", { domain: string; organization: string | null }>
```

Alternate import: Members.DomainSummary from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domain-summary.tsx

### Members.AddDomain

action · Element: form

The form that allows a whole domain into this organization.

Drawn only where every condition of claiming is met. It used to be drawn to everybody —
the access object it checked is never falsy — so a member, or somebody with no
organization open, got a form whose only possible answer was a refusal about
permission. That is what read as a feature that does not work.
→ docs/rules/capability-honesty.md

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
PartProps<"form", { pending: boolean; error: string | null }>
```

Alternate import: Members.AddDomain from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/add-domain.tsx

### Members.Domains

content · Element: div

Letting a whole private domain join. DomainAccess opens where every condition of claiming is met, and NoDomainAccess says which one is not, as a different next step each. AddDomain is the form, with DomainInput (opening on the person's own domain), DomainRole, DomainSummary (what allowing it will do), DomainSubmit and DomainError. Domains repeats the claims and RemoveDomain removes one. Public mail domains are refused.

Import: @slices.sh/auth/headless

Capability: organizations

Members.Domains renders its own scope.

```tsx
DomainProps

type DomainProps = PartProps<"div", { domain: string; role: string }>
```

Alternate import: Members.Domains from @slices.sh/auth/headless/members

Source: @slices.sh/auth/src/headless/members/domains.tsx

### User.Root

scope

Says which auth the User parts below read, and whether they may do without it. The parts need
no root: left out, they read the nearest auth root's config, or "default", and render nothing
without auth. `<User.Root enabled="optional">` is for the one who wants that said in the code.

Import: @slices.sh/auth/headless

```tsx
RootProps & AuthRootProps
```

Alternate import: User.Root from @slices.sh/auth/headless/user

Alternate import: User.Root from @slices.sh/auth/headless/passkeys

Alternate import: User.Root from @slices.sh/auth/headless/two-factor

Source: @slices.sh/auth/src/headless/user/root.tsx

### Organization.Active

gate

Item is a <button> repeated once per organization with { active, pending }; clicking makes it active. Personal switches to the personal account. Active renders its children with the active organization in scope, and nothing on the personal account; None is its mirror, once the answer is in. Switcher holds the searchable list: Switcher.Surface maps onto a Popover or a Sheet, Switcher.Options, Input, List, Item, Empty and Error search and list, and Switcher.Shortcuts binds keys to switch.

Import: @slices.sh/auth/headless

Capability: organizations

```tsx
GateProps
```

Alternate import: Organization.Active from @slices.sh/auth/headless/organization

Source: @slices.sh/auth/src/headless/organization/active.tsx
