# Agreement

> Consent and codes of conduct: an "I agree" button that records who and when, never a checkbox. Once agreed, the record shows in its place with the person's photo.

- Name: `agreement`
- Status: beta
- Categories: forms
- File: the install writes `agreement.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/avatar`, `@arbokui/button`, `@arbokui/motion-timing`. The install adds them.
- Gallery: https://arbokui.com/components/agreement
- Registry item: https://arbokui.com/r/agreement.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/agreement
   ```

## Use it for

- Codes of conduct, consents, and terms that someone must agree to, by name.
- Store the record in onAgree and pass it back as agreed: { person, photo, at }. The record showing in place is the confirmation, so no toast repeats it.
- A guardian agreeing for a junior passes forPerson. The record then says who it was for.
- A consent that can be taken back, like photo consent, passes onWithdraw.
- A required one left unagreed takes error, set when the form is sent. Its title and button edge turn red, its button glows once, and nothing changes size. Say what is missing on the button that was pressed, with shake() and new words, so it never moves.
- In a stack, each sits on the sheet with no box, divided by a faint line. Pass divider={false} on the first one. The sheet's own Continue is the main action, so the buttons stay outlines.
- When it is the surface's only decision, pass main, and the button is solid brand.

## Avoid it for

- Settings that are just on or off. Use a switch.
- Picking among options. Use choice chips or a radio group.
- A checkbox for consent. A button that records who and when says more, and cannot be ticked by accident.

## Props

- `title`: `ReactNode`. What is agreed to, like "Code of conduct". It names the agreement.
- `text`: `ReactNode`. The short version, in plain words.
- `link`: `{ label: ReactNode; href?: string; onClick?: () => void }`. The full text: a link with href, or a button with onClick.
- `forPerson`: `{ person: string; photo?: string }`. Who it is for, when someone agrees on their behalf.
- `agreed`: `{ person: string; photo?: string; at?: string | Date } | null`. The record. Once set, it shows in place of the button: "Agreed by Anna Price · Tue 29 Sept, 18:04".
- `onAgree`: `() => void`. Store the record here, then pass it back as agreed.
- `onWithdraw`: `() => void`. Shows Withdraw on the record. Clear agreed here.
- `error`: `ReactNode`. For a required one left unagreed. The title and button edge turn red and the button glows once, with no change in size. The words describe the button for screen readers.
- `divider`: `boolean`. Default `true`. A faint line above it that fades out at both ends. Pass false on the first of a stack.
- `agreeLabel`: `ReactNode`. Default `"I agree"`. The button's words.
- `main`: `boolean`. Default `false`. The surface's only decision: the button is solid brand. Off, it is an outline.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Text, button, and photo size.
- `loading`: `boolean`. Default `false`. The button shows a spinner while the record is saved.
- `disabled`: `boolean`. Default `false`. Blocks the button and Withdraw.
- `locale`: `string`. Default `"en-GB"`. Formats the record's date and time.
- `timeZone`: `string`. The zone the record's time shows in, like "Europe/London". By default, wherever it renders. Pass one when a server renders the page.
- `className`: `string`. Classes for the root.

## Accessibility

- The root is a group named by the title, so each "I agree" in a stack is heard with what it agrees to.
- An error is described on the button. It is not announced here: the press that found it says it out loud, like a role="alert" beside the button that was pressed.
- The glow follows the --motion-\* tokens, so reduced motion skips it. The red title and edge stay.
- Once agreed, focus moves from the button to the record, so screen readers read who agreed and when. After Withdraw it moves back to the button.
- The record's date is a \<time> element with a machine-readable dateTime.
- The faint divider is hidden from screen readers. In forced-colors mode it draws as a plain border.

## Examples

### In a stack

```tsx
<div className="flex flex-col gap-4">
  <Agreement
    divider={false}
    title="Code of conduct"
    text="Treat everyone with respect, keep shared spaces tidy, and report problems early."
    link={{ label: "Read the code of conduct", href: "/conduct" }}
    agreed={{ person: "Anna Price", at: "2026-09-29T18:04" }}
  />
  <Agreement
    title="Photo consent"
    forPerson={{ person: "Mia Price" }}
    error="The club needs photo consent to share team photos."
  />
</div>
```

### The only decision

```tsx
<Agreement main title="Terms of service" text="Invoices are due on the first day of each month." />
```
