> For AI agents: the complete documentation index is available at https://wallarm.github.io/design-system/llms.txt, the full documentation bundle is available at https://wallarm.github.io/design-system/llms-full.txt.

# ChatInput

The composer at the bottom of the Wally chat: a growing textarea where Enter sends and Shift+Enter adds a line, with the send button turning into Stop while a reply streams. It is controlled — the app owns the draft and the running state — and any actions such as `ChatPermissions` go between the textarea and `ChatInputSubmit`.

[Source](https://github.com/wallarm/design-system/tree/main/packages/design-system/src/components/ChatInput) · [Storybook](https://wallarm.github.io/design-system/storybook/?path=/docs/ai-chatinput--overview)

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const Default = () => {
  const [value, setValue] = useState('');
  const [isRunning, setIsRunning] = useState(false);

  const handleSubmit = () => {
    setValue('');
    setIsRunning(true);
  };

  const handleStop = () => {
    setIsRunning(false);
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        value={value}
        onValueChange={setValue}
        isRunning={isRunning}
        onSubmit={handleSubmit}
        onStop={handleStop}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

## Usage

```tsx
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';
```

## Examples

### Typing

A multi-line draft — the textarea grows with its content up to eight lines, then scrolls.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const Typing = () => {
  const [value, setValue] = useState(
    'Why was 203.0.113.42 blocked yesterday?\nShow the attacks it sent\nand the rule that matched them.',
  );
  const [isRunning, setIsRunning] = useState(false);

  const handleSubmit = () => {
    setValue('');
    setIsRunning(true);
  };

  const handleStop = () => {
    setIsRunning(false);
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        value={value}
        onValueChange={setValue}
        isRunning={isRunning}
        onSubmit={handleSubmit}
        onStop={handleStop}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

### Streaming

While a reply streams the button becomes Stop; its tooltip mentions that Esc in the textarea does the same.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const Streaming = () => {
  const [value, setValue] = useState('');
  const [isRunning, setIsRunning] = useState(true);

  const handleSubmit = () => {
    setValue('');
    setIsRunning(true);
  };

  const handleStop = () => {
    setIsRunning(false);
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        value={value}
        onValueChange={setValue}
        isRunning={isRunning}
        onSubmit={handleSubmit}
        onStop={handleStop}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

### Disabled

Disabled as a whole, e.g. while the assistant is unavailable.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const Disabled = () => {
  const [value, setValue] = useState('Draft kept');

  const handleSubmit = () => {
    setValue('');
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        disabled
        value={value}
        onValueChange={setValue}
        onSubmit={handleSubmit}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

### With context

Attached context sits in a blue bar behind the box; pass `context` only when there are chips.

```tsx
import { type MouseEvent, useState } from 'react';
import { Badge } from '@wallarm-org/design-system/Badge';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';
import { Quote } from '@wallarm-org/design-system/icons';
import { TagClose } from '@wallarm-org/design-system/Tag';

export const WithContext = () => {
  const [value, setValue] = useState('');
  const [chips, setChips] = useState(['Attacks', 'Incidents']);

  const handleContextRemove = (event: MouseEvent<HTMLButtonElement>) => {
    const chip = event.currentTarget.value;
    setChips(prev => prev.filter(item => item !== chip));
  };

  const handleSubmit = () => {
    setValue('');
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        value={value}
        onValueChange={setValue}
        onSubmit={handleSubmit}
        context={chips.map(chip => (
          <Badge key={chip} type='secondary' color='blue' data-testid={`context-${chip}`}>
            <Quote />
            {chip}
            <TagClose
              aria-label={`Remove ${chip}`}
              value={chip}
              className='rounded-4 focus-visible:ring-3 focus-visible:ring-focus-primary'
              onClick={handleContextRemove}
            />
          </Badge>
        ))}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

### With settings

`ChatPermissions` goes between the textarea and the submit, on the second row.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';
import { ChatPermissions, ChatPermissionsContent, ChatPermissionsItem, type ChatPermissionsItemCheckedChangeDetails, ChatPermissionsTrigger } from '@wallarm-org/design-system/ChatPermissions';

export const WithSettings = () => {
  const [value, setValue] = useState('');
  const [allowed, setAllowed] = useState({ search_attacks: true, propose_add_to_denylist: true });

  const handlePermissionChange = ({ checked, name }: ChatPermissionsItemCheckedChangeDetails) => {
    setAllowed(prev => ({ ...prev, [name]: checked }));
  };

  const handleSubmit = () => {
    setValue('');
  };

  return (
    <div className='flex h-[320px] items-end'>
      <div className='w-[400px]'>
        <ChatInput
          data-testid='chat-input'
          value={value}
          onValueChange={setValue}
          onSubmit={handleSubmit}
        >
          <ChatInputTextarea />
          <ChatPermissions data-testid='permissions'>
            <ChatPermissionsTrigger />
            <ChatPermissionsContent>
              {Object.entries(allowed).map(([name, checked]) => (
                <ChatPermissionsItem
                  key={name}
                  name={name}
                  title={name}
                  checked={checked}
                  onCheckedChange={handlePermissionChange}
                />
              ))}
            </ChatPermissionsContent>
          </ChatPermissions>
          <ChatInputSubmit />
        </ChatInput>
      </div>
    </div>
  );
};
```

### No disclaimer

`disclaimer={null}` drops the line under the box.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const NoDisclaimer = () => {
  const [value, setValue] = useState('');

  const handleSubmit = () => {
    setValue('');
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        disclaimer={null}
        value={value}
        onValueChange={setValue}
        onSubmit={handleSubmit}
      >
        <ChatInputTextarea />
        <ChatInputSubmit />
      </ChatInput>
    </div>
  );
};
```

### Analytics

`data-analytics-*` goes straight on `ChatInputTextarea` and `ChatInputSubmit`, which render the real nodes.

```tsx
import { useState } from 'react';
import { ChatInput, ChatInputSubmit, ChatInputTextarea } from '@wallarm-org/design-system/ChatInput';

export const Analytics = () => {
  const [value, setValue] = useState('');

  const handleSubmit = () => {
    setValue('');
  };

  return (
    <div className='w-[400px]'>
      <ChatInput
        data-testid='chat-input'
        value={value}
        onValueChange={setValue}
        onSubmit={handleSubmit}
      >
        <ChatInputTextarea data-analytics-id='WALLY_PROMPT' />
        <ChatInputSubmit
          data-analytics-id='WALLY_SEND'
          data-analytics-props='{"surface":"wally"}'
        />
      </ChatInput>
    </div>
  );
};
```

## Props

| Prop             | Type                                                 | Default              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ---------------- | ---------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| value \*         | `string`                                             |                      | Controlled only — the app owns the draft.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| onValueChange \* | `(value: string) => void`                            |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| onSubmit \*      | `(value: string) => void`                            |                      | Receives `value.trim()`. Not called when the value is empty, while `isRunning`, or when `disabled`. The form's native submit is prevented and focus returns to the textarea.                                                                                                                                                                                                                                                                                                                         |
| onStop           | `(() => void) \| undefined`                          |                      | Stop-button click, and Esc in the textarea while `isRunning`. Inside a Drawer/Dialog that Esc stops the run and doesn't close the layer; no host `onEscapeKeyDown` is needed. While running, a consumer `onKeyDown` can't cancel the Esc stop with `preventDefault` (the textarea has already claimed the key from the layer). Without `onStop`, Esc is left to the layer. A consumer popup anchored to the textarea (mention/autocomplete) keeps Esc while the textarea has `aria-expanded='true'`. |
| isRunning        | `boolean \| undefined`                               | `false`              | Swaps Send for Stop and blocks submit. CopilotKit: `agent.isRunning`.                                                                                                                                                                                                                                                                                                                                                                                                                                |
| disabled         | `boolean \| undefined`                               | `false`              |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| context          | `ReactNode`                                          |                      | Attached-context chips, shown in a bar behind the box. Nothing to render (`null`, `false`, `[]`) hides the bar.                                                                                                                                                                                                                                                                                                                                                                                      |
| disclaimer       | `ReactNode`                                          | `DEFAULT_DISCLAIMER` | Line under the box. `null`/`false` hides it.                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| title            | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| defaultChecked   | `boolean \| undefined`                               |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| defaultValue     | `string \| number \| readonly string[] \| undefined` |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| autoFocus        | `boolean \| undefined`                               |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| id               | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| tabIndex         | `number \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| rel              | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| onChange         | `ChangeEventHandler<T, Element> \| undefined`        |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| name             | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| target           | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| autoComplete     | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| data-testid      | `string \| undefined`                                |                      |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

### ChatInputSubmit

| Prop           | Type                                                 | Default         | Description                                    |
| -------------- | ---------------------------------------------------- | --------------- | ---------------------------------------------- |
| sendLabel      | `string \| undefined`                                | `Send`          | Accessible name while idle.                    |
| stopLabel      | `string \| undefined`                                | `Stop response` | Accessible name and tooltip while `isRunning`. |
| title          | `string \| undefined`                                |                 |                                                |
| defaultChecked | `boolean \| undefined`                               |                 |                                                |
| defaultValue   | `string \| number \| readonly string[] \| undefined` |                 |                                                |
| autoFocus      | `boolean \| undefined`                               |                 |                                                |
| id             | `string \| undefined`                                |                 |                                                |
| tabIndex       | `number \| undefined`                                |                 |                                                |
| rel            | `string \| undefined`                                |                 |                                                |
| onChange       | `ChangeEventHandler<T, Element> \| undefined`        |                 |                                                |
| disabled       | `boolean \| undefined`                               |                 |                                                |
| name           | `string \| undefined`                                |                 |                                                |
| value          | `string \| number \| readonly string[] \| undefined` |                 |                                                |
| data-testid    | `string \| undefined`                                |                 |                                                |

### ChatInputTextarea

| Prop           | Type                                                      | Default | Description |
| -------------- | --------------------------------------------------------- | ------- | ----------- |
| title          | `string \| undefined`                                     |         |             |
| defaultChecked | `boolean \| undefined`                                    |         |             |
| autoFocus      | `boolean \| undefined`                                    |         |             |
| id             | `string \| undefined`                                     |         |             |
| tabIndex       | `number \| undefined`                                     |         |             |
| rel            | `string \| undefined`                                     |         |             |
| onChange       | `ChangeEventHandler<T, HTMLTextAreaElement> \| undefined` |         |             |
| disabled       | `boolean \| undefined`                                    |         |             |
| name           | `string \| undefined`                                     |         |             |
| autoComplete   | `string \| undefined`                                     |         |             |
| maxLength      | `number \| undefined`                                     |         |             |
| minLength      | `number \| undefined`                                     |         |             |
| placeholder    | `string \| undefined`                                     |         |             |
| readOnly       | `boolean \| undefined`                                    |         |             |
| required       | `boolean \| undefined`                                    |         |             |
| data-testid    | `string \| undefined`                                     |         |             |

## Explorer

