# Input group

Puts icons, text, keys or buttons inside the border of an input or textarea.

- **Import:** `import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea } from "@booleanpress/ui/input-group"`
- **Page:** <https://ui.booleanpress.com/components/input-group> · @booleanpress/ui 0.1.0

## Usage

`InputGroup` draws one border and focus ring around its parts. Put an `InputGroupInput` (or `InputGroupTextarea`) in it, and `InputGroupAddon` parts for what sits beside or above the text. An addon holds an icon, `InputGroupText`, a `kbd` or an `InputGroupButton`.

```tsx
import { SearchIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@booleanpress/ui/input-group"

export function LogSearch() {
  return (
    <InputGroup>
      <InputGroupInput aria-label="Search the email log" />
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
    </InputGroup>
  )
}
```

`align` on the addon is `inline-start` (default), `inline-end`, `block-start` or `block-end`; the inline values follow the text direction. Block addons stack the group and are for textareas. `InputGroupButton` is a ghost `Button` with `type="button"` and sizes `xs` (default), `sm`, `icon-xs`, `icon-sm`. Clicking an addon (not a button in it) focuses the group's `input`. For a ready-made secret field use `PasswordInput`. The input and textarea accept the attributes of the native element, so state and value work as they do there.

## Examples

### Icon

An icon before the text; clicking it focuses the input.

```tsx
import { SearchIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@booleanpress/ui/input-group"

export default function InputGroupIcon() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput aria-label="Search the email log" placeholder="Search the email log" />
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### Prefix and suffix

`InputGroupText` at both ends gives the fixed parts of an address.

```tsx
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@booleanpress/ui/input-group"

export default function InputGroupTextExample() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupAddon>
        <InputGroupText>https://</InputGroupText>
      </InputGroupAddon>
      <InputGroupInput aria-label="Tracking domain" placeholder="track" />
      <InputGroupAddon align="inline-end">
        <InputGroupText>.example.com</InputGroupText>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### Button

An icon button at the end, named with `aria-label`; clicking it does not move focus to the input.

```tsx
import { useState } from "react"
import { CheckIcon, CopyIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@booleanpress/ui/input-group"

export default function InputGroupButtonExample() {
  const [copied, setCopied] = useState(false)

  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput aria-label="Webhook URL" defaultValue="https://example.com/hooks/delivery" readOnly />
      <InputGroupAddon align="inline-end">
        <InputGroupButton aria-label={copied ? "Copied" : "Copy the webhook URL"} size="icon-xs" onClick={() => setCopied(true)}>
          {copied ? <CheckIcon /> : <CopyIcon />}
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### Textarea with a toolbar

A `block-end` addon holds a counter and the send button below the text.

```tsx
import { SendIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupTextarea } from "@booleanpress/ui/input-group"

export default function InputGroupTextareaExample() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupTextarea aria-label="Reply" placeholder="Write a reply" rows={3} />
      <InputGroupAddon align="block-end">
        <InputGroupText>0 / 2000</InputGroupText>
        <InputGroupButton variant="default" size="sm" className="ms-auto">
          <SendIcon />
          Send
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### Disabled

`data-disabled="true"` on the group dims the addons; `disabled` on the input stops editing.

```tsx
import { MailIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@booleanpress/ui/input-group"

export default function InputGroupDisabled() {
  return (
    <InputGroup className="max-w-sm" data-disabled="true">
      <InputGroupInput aria-label="Sender" defaultValue="alerts@example.com" disabled />
      <InputGroupAddon>
        <MailIcon />
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### Invalid

`aria-invalid` on the input draws the error border and ring around the whole group.

```tsx
import { MailIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@booleanpress/ui/input-group"

export default function InputGroupInvalid() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-1.5">
      <InputGroup>
        <InputGroupInput aria-label="Recipient" defaultValue="sam@" aria-invalid aria-describedby="recipient-error" />
        <InputGroupAddon>
          <MailIcon />
        </InputGroupAddon>
      </InputGroup>
      <p id="recipient-error" className="text-sm text-destructive">
        Enter a full email address.
      </p>
    </div>
  )
}
```

## Accessibility

**Semantics.** The group and each addon are `div`s with `role="group"`; the control is a native `input` or `textarea`. An `InputGroupButton` is a native `button`.

**Labels.** Name the control with `aria-label` or a `Label htmlFor` (an addon's text does not name it). Name an icon-only button with `aria-label`. An icon is decorative: it is not announced.

**Focus.** Focus stays on the control or a button. The ring is drawn on the group when the control has keyboard focus.

**Known limits.**

- The groups have no accessible name. Name the control, not the group.
- Clicking an addon focuses only an `input`, not an `InputGroupTextarea`.
- `InputGroupText` has no `data-slot`.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Moves between the control and any buttons in the addons, in document order (the order of the JSX, not the visual order). |
| Enter + Space | Activates a focused `InputGroupButton`. |

## API

### InputGroup

Renders a `div` and passes it every other prop.

### InputGroupAddon

Renders a `div` and passes it every other prop.

### InputGroupButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |

### InputGroupText

Renders a `span` and passes it every other prop.

### InputGroupInput

Renders a `input` and passes it every other prop.

### InputGroupTextarea

Renders a `textarea` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="input-group"` (InputGroup), `data-slot="input-group-addon"` (InputGroupAddon), `data-slot="input-group-control"` (InputGroupInput), `data-slot="input-group-control"` (InputGroupTextarea), and `data-align`, `data-size`.

## Theming

| Token | Used for |
| --- | --- |
| `--control` | border |
| `--destructive` | border, focus ring |
| `--input` | border, background |
| `--muted-foreground` | text |
| `--ring` | border, focus ring |
