> ## Documentation Index
> Fetch the complete documentation index at: https://developers.reintersect.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interactive components

> Button, Actions, Menu, ChoiceGroup, Form, Toggle, Select, TextInput, DateTimePicker and Tabs.

> Reintersect apps are in developer preview. Names and limits can still change before general
> release.

Interactive components run your [actions](/blocks/state-and-actions) or collect
[viewer input](/blocks/viewer-inputs). Read-only views, such as share pages and connected workspaces,
hide actions, menus, forms and inputs, and keep link and copy buttons.

Controls have no `disabled` prop, so hide a control that doesn't apply. When a member can't act on
a block, for example because the circle is read-only for them or the block is final, Reintersect
dims the controls and shows the reason under them.

## Button

A button that runs an action, opens a link, copies text or opens a section. Pick one with `type`,
which defaults to `"action"`. Each type takes exactly the props it needs, and a prop for another
type is a type error.

Buttons go in two places: the block's root [`Actions`](#actions), where Reintersect decides which one
is primary, and a [`Row`](/components/layout#row), where they're small accessory buttons. A form's
submit button comes from its `submitLabel`.

```tsx theme={null}
<Actions>
  <Button label="Approve" action={actions.approve} />
  <Button
    label="Reject"
    action={actions.reject}
    confirm={{ title: "Reject this request?", body: "The requester gets a notification." }}
  />
  <Button type="link" label="Open in Linear" icon="external-link" href={issue.url} />
  <Button type="copy" label="Copy ID" icon="copy" copy={issue.id} />
  <Button type="toggle" label="Add a note" section={sections.note} />
</Actions>
```

| `type` | Required props | What it does |
| - | - | - |
| `"action"` (default) | `action` | Runs a bound action from the render context. If the action is `destructive`, `confirm` is required, and Reintersect shows the confirm dialog before calling your app. |
| `"link"` | `href` | Opens an `https://` link. |
| `"copy"` | `copy` | Copies the text to the clipboard and shows a check on the button for a moment. |
| `"toggle"` | `section` | Opens and closes a `Section` with that handle, in the viewer's browser only. |

Props every type accepts:

| Prop | Type | Required | Description |
| - | - | - | - |
| `label` | `string` | Yes | The button text, up to 32 characters, unique within its row. It's also the accessible name and tooltip when `iconOnly`. |
| `icon` | `ActionIconName` | No | Shown before the label. Any [icon](/components/overview#icons) except the ones Reintersect keeps for its own controls. |
| `iconOnly` | `boolean` | No | Show only the icon; the label becomes the tooltip. Needs `icon`. |
| `audience` | `Audience` | No | Limit who sees it. Only on buttons in the root `Actions`. See [Actions](#actions). |

There's no prop for emphasis or size. The first button in the root `Actions` that isn't destructive
is primary, the rest are secondary, and buttons in a `Row` are small accessories.

### Destructive actions and confirm

When an action is defined with `destructive: true`, the SDK marks every button and menu item bound to
it with `intent: "destructive"`, and `confirm` becomes required: leaving it out is a type error and
a runtime error. A destructive button is outlined in the danger colour and is never primary.

`confirm` is `{ title, body? }`: a title of up to 80 characters and an optional body of up to 280.
The dialog's confirm button repeats the label of the button that opened it, its **Cancel** button
gets focus first, and it uses the danger style only for destructive actions. A non-destructive
action can still ask for confirmation with `confirm`.

Pass an action with arguments as `actions.move.with({ id, status })`; leaving out `.with()` when the
action declares `args` is a type error.

In agent text, most buttons read as `[Label]` and link buttons as `[Label](https://…)`.

## Actions

The block's row of buttons. Use it for the main things a viewer can do with the block. It's the last
part of the block's body, before the `Footer`, and a block has at most one. Sections, tabs, columns,
callouts and stacks can't hold an `Actions` row: put their buttons here, or in a `Row` next to the
text they act on.

```tsx theme={null}
<Actions>
  <Button label="Merge" action={actions.merge} audience="admins" />
  <Button type="link" label="Open pull request" href={pr.url} />
  <Menu label="More" icon="ellipsis">
    <MenuItem type="copy" label="Copy branch name" copy={pr.branch} />
    <MenuItem label="Close" action={actions.close} confirm={{ title: "Close this pull request?" }} />
  </Menu>
</Actions>
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `children` | `Button` and `Menu` elements | Yes | One to five buttons, then at most one `Menu`, or just a `Menu`. |
| `audience` | `Audience` | No | Limit who sees the whole row. |

Each `Button` and `Menu` in the row can take its own `audience`. Viewers see only the buttons their
audience allows, and the primary button is the first visible one that isn't destructive. In the
example, admins see **Merge** as the primary button, and everyone else sees **Open pull request** as
primary. When every button in the row is hidden from a viewer, the row isn't drawn for them. Labels
are unique across the whole row, including hidden buttons and the menu's label.

When the row is narrower than 280px, the buttons stack at full width, primary first.

## Menu

A button that opens a list of actions, links and copy items. Use it when a block has more than a
few secondary actions. A menu goes at the end of the root `Actions` or in a `Row`.

```tsx theme={null}
<Menu label="More" icon="ellipsis">
  <MenuItem label="Reassign to me" action={actions.assignToMe} />
  <MenuItem type="link" label="Open in Linear" href={issue.url} />
  <MenuItem type="copy" label="Copy link" copy={issue.url} />
  <MenuSeparator />
  <MenuItem
    label="Close issue"
    icon="circle-x"
    action={actions.close}
    confirm={{ title: "Close this issue?" }}
  />
</Menu>
```

| Prop | Component | Type | Required | Description |
| - | - | - | - | - |
| `label` | `Menu` | `string` | Yes | The menu button's label, up to 32 characters. |
| `icon` | `Menu` | `ActionIconName` | No | The menu button's icon. |
| `children` | `Menu` | `MenuItem` and `MenuSeparator` | Yes | At most 12 items, with unique labels. |
| `audience` | `Menu` | `Audience` | No | Limit who sees it. Only on a menu in the root `Actions`. |
| `label` | `MenuItem` | `string` | Yes | Item text, up to 32 characters. |
| `icon` | `MenuItem` | `ActionIconName` | No | Item icon. |
| `type` | `MenuItem` | `"action" \| "link" \| "copy"` | No | Default `"action"`. |
| `action`, `confirm` | `MenuItem` | bound action, `ConfirmSpec` | For actions | As on `Button`. `confirm` is required for destructive actions. |
| `href` | `MenuItem` | `string` | For links | An `https://` link. |
| `copy` | `MenuItem` | `string` | For copy items | Text to copy. |

`MenuSeparator` takes no props. Separators at the start or end of a menu, or next to another
separator, aren't drawn. A menu with a single item is drawn as a secondary button with that item's
label. A copy item confirms inline, without a toast. In agent text, a menu reads as
`Label: [Item] [Item]`.

## ChoiceGroup

A set of choices bound to an input the block declares with `choice()`. Use it for polls, votes and
RSVPs. Each member's pick is stored per viewer, and Reintersect keeps the counts. See
[Viewer inputs](/blocks/viewer-inputs).

```tsx theme={null}
<ChoiceGroup
  input={inputs.verdict}
  label="Your call"
  appearance="segmented"
  labels={{ go: "Go", nogo: { label: "No go", description: "Say why in the thread." } }}
/>
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `input` | `InputHandle` | Yes | From `inputs` in the render context. |
| `label` | `string` | Yes | The question, up to 60 characters. It's always visible. |
| `labels` | `Record<value, string \| { label; description? }>` | Yes | A label for every declared value, up to 60 characters and unique within the group, with an optional description of up to 280. A missing value is a type error. |
| `appearance` | `"list" \| "segmented" \| "chips"` | No | Default `"list"`. A preference: see below. |
| `scope` | `"viewer" \| "shared"` | No | Default `"viewer"`: each member has their own pick. |
| `results` | `"none" \| "host" \| "integration"` | No | Default `"host"`: Reintersect shows live counts, fill bars and who voted. `"integration"` hands results to your app: the `onChange` action runs after each pick and the block re-renders from the state it returns. Without `onChange`, Reintersect falls back to its own counts. `"none"` hides counts. |
| `clearable` | `boolean` | No | Let members remove their pick. |
| `onChange` | bound action | No | A non-destructive action Reintersect runs after each pick, with the new tallies. |
| `audience` | `Audience` | No | Limit who sees it. |

`appearance` is a request that Reintersect downgrades when the choices won't fit it: `"segmented"`
is used for 2 to 4 choices of up to 16 characters each, `"chips"` for up to 8 choices, and anything
else shows as a list. Fill bars always come from the counts.

Whether the input allows several picks and whether voters stay anonymous comes from the input's
declaration (`choice(values, { multiple, anonymous })`), not from the component. To stop new picks,
for example when a poll closes, return `final` from the closing action or stop rendering the group.
Agent text lists each choice with its current count.

## Form

Collects input for one action. Use it when an action needs more than a click, such as a reason for a
rejection. The action declares an `input` schema, and `Form` hands you one typed `field` per property
through a render prop.

```tsx theme={null}
<Section handle={sections.reject} title="Reject" collapsed>
  <Form action={actions.reject} submitLabel="Reject request">
    {(fields) => (
      <>
        <TextInput field={fields.reason} label="Reason" multiline required maxLength={500} />
        <Toggle field={fields.notifyRequester} label="Tell the requester" />
      </>
    )}
  </Form>
</Section>
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `action` | bound action | Yes | An action that declares `input`. A bound action without `input` is a type error. |
| `submitLabel` | `string` | Yes | The submit button's label, up to 32 characters. |
| `children` | `(fields) => JSX` | Yes | Receives one `field` handle per input property. |
| `audience` | `Audience` | No | Limit who sees it. |

The form ends with its own row holding only the submit button, so there's no cancel button. A form's
submit button is primary when the block has no root `Actions` and this is its only form; otherwise
it's secondary. A form sits at the block, section or tab level, and forms don't nest.

Field values live in the viewer's browser until they submit. If your action returns `fieldErrors`,
Reintersect shows each message next to its field. See [Sections and forms](/blocks/sections-and-forms).

## TextInput

A single-line or multi-line text field inside a `Form`. Use it for free text, such as a title or a
reason.

```tsx theme={null}
<TextInput field={fields.title} label="Title" placeholder="What changed?" required maxLength={120} />
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `field` | `Field<string>` | Yes | From the form's render prop. |
| `label` | `string` | Yes | Field label, up to 60 characters. Labels are always visible. |
| `placeholder` | `string` | No | Placeholder text. |
| `multiline` | `boolean` | No | A text area. |
| `required` | `boolean` | No | Block submit while empty. |
| `maxLength` | `number` | No | 1 to 2,000 characters. |
| `value` | `string` | No | Initial value. |
| `audience` | `Audience` | No | Limit who sees it. |

## Toggle

An on and off switch inside a `Form`. Use it for a yes or no choice, such as whether to notify a
channel.

```tsx theme={null}
<Toggle field={fields.notify} label="Notify the channel" description="Posts a reply when you submit." />
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `field` | `Field<boolean>` | Yes | From the form's render prop. |
| `label` | `string` | Yes | Field label, up to 60 characters. |
| `description` | `string` | No | A detail line, up to 280 characters. |
| `value` | `boolean` | No | Initial value. |
| `audience` | `Audience` | No | As on `TextInput`. |

## DateTimePicker

A date or date and time field inside a `Form`. Use it to ask for a date, such as a due date.

```tsx theme={null}
<DateTimePicker field={fields.due} label="Due" mode="date" min={new Date()} required />
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `field` | `Field<string \| Date>` | Yes | From the form's render prop. |
| `label` | `string` | Yes | Field label, up to 60 characters. |
| `mode` | `"date" \| "datetime"` | No | Pick a date, or a date and time. |
| `required` | `boolean` | No | Block submit while empty. |
| `value`, `min`, `max` | `string \| Date` | No | Initial value and bounds. |
| `audience` | `Audience` | No | As on `TextInput`. |

## Select

A dropdown inside a `Form`, with static options or options your app searches. Use it to pick one
value from a list, such as a priority or a project.

```tsx theme={null}
<Form action={actions.assign} submitLabel="Assign">
  {(fields) => (
    <>
      <Select
        field={fields.priority}
        label="Priority"
        options={{ low: "Low", medium: "Medium", high: "High" }}
      />
      <Select
        field={fields.project}
        label="Project"
        options={actions.searchProjects}
        placeholder="Search projects"
        value={props.projectId}
        selectedLabel={props.projectName}
      />
    </>
  )}
</Form>
```

| Prop | Type | Required | Description |
| - | - | - | - |
| `field` | `Field<V>` | Yes | From the form's render prop. |
| `label` | `string` | Yes | Field label, up to 60 characters. |
| `options` | `Record<V, string>` or a bound action | Yes | Up to 100 static options with unique labels of up to 60 characters, or a search action. |
| `placeholder` | `string` | No | Placeholder text. |
| `value` | `V` | No | Initial value. |
| `selectedLabel` | `string` | With a search value | The label to show for `value` before anyone searches. Search selects only; a search select with a `value` needs it. |
| `audience` | `Audience` | No | As on `TextInput`. |

A search action receives the typed text as `query` in its context and returns `{ options }`, at most
50 items of `{ value, label, description? }`. Reintersect debounces typing and starts searching at two
characters. If the search fails, the menu says "Couldn't search. Try again." with a retry button.
Mark search actions `readOnly: true` so agents and approvals treat them as reads:

```ts theme={null}
export const searchProjects = defineAction(
  "searchProjects",
  { readOnly: true, description: "Find projects by name" },
  async ({ query }) => ({
    options: (await findProjects(query ?? "")).map((project) => ({ value: project.id, label: project.name })),
  }),
);
```

## Tabs and Tab

Groups content under tabs. Use it when a block has several views of one thing, such as a pull
request's checks and files. Switching tabs happens in the viewer's browser. Read-only views and agent
text show every tab, one after the other, under its label.

```tsx theme={null}
<Tabs label="Pull request">
  <Tab label="Checks" icon="list-checks" count={3}>
    <Steps>
      <Step label="lint" state="success" />
    </Steps>
  </Tab>
  <Tab label="Files" count={12}>
    <ItemList>
      <Item>src/export.ts</Item>
    </ItemList>
  </Tab>
</Tabs>
```

| Prop | Component | Type | Required | Description |
| - | - | - | - | - |
| `children` | `Tabs` | `Tab` elements | Yes | Two to six tabs. Tabs don't nest. |
| `label` | `Tabs` | `string` | No | An accessible name for the group, up to 60 characters. |
| `audience` | `Tabs` | `Audience` | No | Limit who sees them. |
| `label` | `Tab` | `string` | Yes | Tab label, up to 32 characters and unique within the tabs. |
| `icon` | `Tab` | `IconName` | No | Tab icon. |
| `count` | `Tab` | `number` | No | A count badge, 0 to 9999. |
| `children` | `Tab` | JSX | Yes | The tab's content. A tab can hold sections, columns and a form. |

`Tabs` sit at the block or section level. Tabs that don't fit the width move into a trailing
**More** menu, and the selected tab always stays visible.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.