---
title: "ChatPrompt"
description: "An enhanced Textarea for submitting prompts in AI chat interfaces."
canonical_url: "https://ui.nuxt.com/docs/components/chat-prompt"
---
# ChatPrompt

> An enhanced Textarea for submitting prompts in AI chat interfaces.

## Usage

The ChatPrompt component renders a `<form>` element and extends the [Textarea](https://ui.nuxt.com/docs/components/textarea) component so you can pass any property such as `icon`, `placeholder`, `autofocus`, etc.

```vue [ChatPromptExample.vue]
<script setup lang="ts">
import type { SelectItem } from '@nuxt/ui'

const input = ref('')
const model = ref('opus-5-5')

const models = [
  { label: 'Opus 5.5', value: 'opus-5-5', icon: 'i-simple-icons-anthropic' },
  { label: 'Sonnet 5.5', value: 'sonnet-5-5', icon: 'i-simple-icons-anthropic' },
  { label: 'Haiku 5.5', value: 'haiku-5-5', icon: 'i-simple-icons-anthropic' }
] satisfies SelectItem[]

function onSubmit() {
  input.value = ''
}
</script>

<template>
  <UChatPrompt v-model="input" :autofocus="false" class="w-full" @submit="onSubmit">
    <template #footer>
      <UButton icon="i-lucide-plus" color="neutral" variant="ghost" size="sm" />

      <div class="flex items-center gap-1.5">
        <USelect
          v-model="model"
          :items="models"
          :icon="models.find(item => item.value === model)?.icon"
          placeholder="Select a model"
          variant="ghost"
          size="sm"
          square
        />

        <UChatPromptSubmit size="sm" />
      </div>
    </template>
  </UChatPrompt>
</template>
```

> [!NOTE]
> 
> The ChatPrompt handles the following events:
> 
> - The form is submitted when the user presses `Enter` or when the user clicks on the submit button. Set the `submit-on-enter` prop to `false` to submit with `Ctrl` + `Enter` (or `Cmd` + `Enter` on macOS) instead, allowing `Enter` to insert a newline.
> - The form is not submitted while the `status` prop is `submitted` or `streaming`, so pressing `Enter` during a response doesn't send a second message.
> - The textarea is blurred when `Esc` is pressed and emits a `close` event.

### Variant

Use the `variant` prop to change the style of the prompt. Defaults to `outline`.

```vue
<template>
  <UChatPrompt variant="soft" />
</template>
```

### Files `Soon`

Use the `v-model:files` directive to let users attach files. They can paste them in the textarea or drop them on the prompt, and each one shows above the textarea as a [ChatAttachment](https://ui.nuxt.com/docs/components/chat-attachment) they can remove. The `#footer` slot receives an `open` function to pick files from a button.

```vue [ChatPromptFilesExample.vue]
<script setup lang="ts">
const input = ref('')
const files = ref<File[]>([])

function onSubmit() {
  input.value = ''
  files.value = []
}
</script>

<template>
  <UChatPrompt
    v-model="input"
    v-model:files="files"
    accept="image/*,application/pdf"
    placeholder="Paste or drop files here..."
    :autofocus="false"
    class="w-full"
    @submit="onSubmit"
  >
    <template #footer="{ open }">
      <UButton
        icon="i-lucide-paperclip"
        color="neutral"
        variant="ghost"
        size="sm"
        aria-label="Attach files"
        @click="open"
      />

      <UChatPromptSubmit size="sm" />
    </template>
  </UChatPrompt>
</template>
```

> [!TIP]
> 
> Use the `attachment` prop to customize the attachments, like `{ variant: 'thumbnail', size: 'xl' }`{lang="ts-type"} for image previews, or the `#files` slot to render them yourself.

> [!NOTE]
> 
> Use the `accept` prop to restrict the file types and set `multiple` to `false` to keep a single file. The form is submitted with files even when the textarea is empty. While a file is dragged over the prompt, its root has `data-dragging="true"`, which you can style with `data-[dragging=true]:`.

> [!TIP]
> 
> Use the `toFileUIParts` utility from `@nuxt/ui/utils/ai` to send the files as data URLs with the AI SDK, and the `#files` slot of [ChatMessages](https://ui.nuxt.com/docs/components/chat-messages) to display them in the conversation.
> 
> ```vue
> <script setup lang="ts">
> import { useChat } from '@ai-sdk/vue'
> import { toFileUIParts } from '@nuxt/ui/utils/ai'
> 
> const input = ref('')
> const files = ref<File[]>([])
> 
> const { messages, status, sendMessage } = useChat()
> 
> async function onSubmit() {
>   const text = input.value
>   const submitted = files.value
> 
>   input.value = ''
>   files.value = []
> 
>   const parts = await toFileUIParts(submitted)
>   sendMessage(text.trim() ? { text, files: parts } : { files: parts })
> }
> </script>
> 
> <template>
>   <UChatMessages :messages="messages" :status="status">
>     <template #files="{ parts }">
>       <UChatAttachment
>         v-for="(part, index) in parts"
>         :key="index"
>         :name="part.filename"
>         :type="part.mediaType"
>         :url="part.url"
>       />
>     </template>
>   </UChatMessages>
> 
>   <UChatPrompt v-model="input" v-model:files="files" @submit="onSubmit">
>     <UChatPromptSubmit :status="status" />
>   </UChatPrompt>
> </template>
> ```

### Queue `Soon`

Use the `v-model:queue` directive to queue the messages submitted while the chat is `submitted` or `streaming` instead of ignoring them. They show in a panel attached above the prompt, where the user can send one now, put it back in the prompt to edit it, or remove it. The `send` event is emitted when one is picked to be sent now, after it moved to the front of the queue.

```vue [ChatPromptQueueExample.vue]
<script setup lang="ts">
import type { ChatPromptQueueItem } from '@nuxt/ui'

const input = ref('')
const queue = ref<ChatPromptQueueItem[]>([
  { text: 'Can you also add a dark mode toggle?' },
  { text: 'And write the tests for it' }
])
</script>

<template>
  <UChatPrompt
    v-model="input"
    v-model:queue="queue"
    status="streaming"
    placeholder="Send a message while it answers..."
    :autofocus="false"
    class="w-full"
  >
    <UChatPromptSubmit status="streaming" />
  </UChatPrompt>
</template>
```

> [!TIP]
> See: #with-queued-messages
> 
> Sending the queued messages is up to you, check the **With queued messages** example for a recipe with the AI SDK.

## Examples

> [!TIP]
> See: /docs/components/chat
> 
> Check the **Chat** overview page for installation instructions, server setup and usage examples.

### With an Editor `4.10+`

Compose the `#body` and `#footer` slots with `v-model:files` to build a rich prompt: file attachments, an [Editor](https://ui.nuxt.com/docs/components/editor) with `@` mentions and `/` commands through [EditorMentionMenu](https://ui.nuxt.com/docs/components/editor-mention-menu), and a mode selector.

```vue [ChatPromptEditorExample.vue]
<script setup lang="ts">
import { Extension } from '@tiptap/core'
import type { EditorMentionMenuItem, SelectItem } from '@nuxt/ui'

const input = ref('')
const mode = ref('auto')
const attachments = ref<File[]>([])

const files: EditorMentionMenuItem[] = [
  { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
  { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
  { label: 'package.json', icon: 'i-vscode-icons-file-type-json' },
  { label: 'README.md', icon: 'i-vscode-icons-file-type-markdown' },
  { label: 'AuthForm.vue', icon: 'i-vscode-icons-file-type-vue' },
  { label: 'useChat.ts', icon: 'i-vscode-icons-file-type-typescript' }
]

const commands: EditorMentionMenuItem[] = [
  { label: 'init', icon: 'i-lucide-sparkles' },
  { label: 'review', icon: 'i-lucide-search-code' },
  { label: 'security-review', icon: 'i-lucide-shield-check' },
  { label: 'clear', icon: 'i-lucide-eraser' },
  { label: 'compact', icon: 'i-lucide-fold-vertical' },
  { label: 'config', icon: 'i-lucide-settings' }
]

const modes = [
  { label: 'Manual', value: 'manual', icon: 'i-lucide-hand' },
  { label: 'Edit automatically', value: 'edit', icon: 'i-lucide-code-xml' },
  { label: 'Plan mode', value: 'plan', icon: 'i-lucide-list-checks' },
  { label: 'Auto mode', value: 'auto', icon: 'i-lucide-zap' }
] satisfies SelectItem[]

// SSR-safe target so the menus aren't clipped by overflow
const appendToBody = false ? () => document.body : undefined

function onSubmit() {
  console.log('submit', input.value, attachments.value)

  input.value = ''
  attachments.value = []
}
</script>

<template>
  <UChatPrompt
    v-model="input"
    v-model:files="attachments"
    class="w-full p-0 gap-0"
    placeholder="Press / to open the command menu"
    :ui="{
      files: 'px-2.5 py-2 border-b border-default',
      footer: 'px-2.5 py-2 border-t border-default'
    }"
    @submit="onSubmit"
  >
    <template #body="{ submit, placeholder }">
      <UEditor
        v-slot="{ editor }"
        v-model="input"
        content-type="markdown"
        :starter-kit="false"
        :placeholder="placeholder"
        class="w-full min-h-12"
        :ui="{ base: 'p-2.5! [&_.mention]:bg-primary/5 [&_.mention]:rounded-sm [&_.mention]:px-0.5 [&_.mention]:py-0.25' }"
        :extensions="[Extension.create({
          name: 'chatPromptSubmit',
          priority: 1000,
          addKeyboardShortcuts: () => ({ Enter: () => (submit(), true) })
        })]"
      >
        <UEditorMentionMenu :editor="editor" char="@" plugin-key="mention" :items="files" :append-to="appendToBody" />
        <UEditorMentionMenu :editor="editor" char="/" plugin-key="command" :items="commands" :append-to="appendToBody" />
      </UEditor>
    </template>

    <template #footer="{ open }">
      <div class="flex items-center gap-0.5">
        <UButton
          icon="i-lucide-plus"
          color="neutral"
          variant="ghost"
          aria-label="Attach files"
          size="sm"
          @click="open"
        />
      </div>

      <div class="flex items-center gap-1">
        <USelect
          v-model="mode"
          :items="modes"
          :icon="modes.find(item => item.value === mode)?.icon"
          color="neutral"
          variant="ghost"
          size="sm"
          square
        />

        <UChatPromptSubmit size="sm" :disabled="!input.trim() && !attachments.length" />
      </div>
    </template>
  </UChatPrompt>
</template>
```

> [!NOTE]
> 
> The `#body` slot replaces the internal textarea and exposes `submit` and `close` handlers, so you can wire the editor's keyboard shortcuts to the form. When a mention menu is open, pressing `Enter` selects the highlighted item instead of submitting.

### As home page

You can also use it in your chat interface home page.

```vue [pages/index.vue] {2,4,8,9,10,11,12,13,14,15,24,26}
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue'

const input = ref('')

const { messages, status, sendMessage } = useChat()

async function onSubmit() {
  sendMessage({ text: input.value })

  // Navigate to chat page after first message
  if (messages.value.length === 1) {
    await navigateTo('/chat')
  }
}
</script>

<template>
  <UDashboardPanel>
    <template #body>
      <UContainer>
        <h1>How can I help you today?</h1>

        <UChatPrompt v-model="input" :status="status" @submit="onSubmit">
          <UChatPromptSubmit :status="status" />
        </UChatPrompt>
      </UContainer>
    </template>
  </UDashboardPanel>
</template>
```

### With queued messages `Soon`

The AI SDK doesn't queue messages: calling `sendMessage` during a response starts a second request next to the current one. With `status`, `UChatPrompt` ignores submits while the chat is `submitted` or `streaming`. Bind `v-model:queue` to keep them instead: the prompt lists the queued messages in a panel above it, where the user can send one now, edit it or remove it, and you send the next one once the chat is `ready` again.

**Nuxt:**

```vue
<script setup lang="ts">
import type { ChatPromptQueueItem } from '@nuxt/ui'
import { useChat } from '@ai-sdk/vue'

const input = ref('')
const queue = ref<ChatPromptQueueItem[]>([])

const { status, error, sendMessage, regenerate, stop } = useChat()

function onSubmit() {
  sendMessage({ text: input.value })
  input.value = ''
}

function sendNext() {
  const item = queue.value.shift()
  if (item) {
    sendMessage({ text: item.text })
  }
}

watch(status, (value) => {
  if (value === 'ready') {
    sendNext()
  }
})

// The message picked to be sent now is at the front of the queue
function onSend() {
  if (status.value === 'submitted' || status.value === 'streaming') {
    stop()
  } else {
    sendNext()
  }
}
</script>

<template>
  <UChatPrompt
    v-model="input"
    v-model:queue="queue"
    :status="status"
    :error="error"
    @submit="onSubmit"
    @send="onSend"
  >
    <UChatPromptSubmit :status="status" @stop="stop()" @reload="regenerate()" />
  </UChatPrompt>
</template>
```

**Vue:**

```vue
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { ChatPromptQueueItem } from '@nuxt/ui'
import { useChat } from '@ai-sdk/vue'

const input = ref('')
const queue = ref<ChatPromptQueueItem[]>([])

const { status, error, sendMessage, regenerate, stop } = useChat()

function onSubmit() {
  sendMessage({ text: input.value })
  input.value = ''
}

function sendNext() {
  const item = queue.value.shift()
  if (item) {
    sendMessage({ text: item.text })
  }
}

watch(status, (value) => {
  if (value === 'ready') {
    sendNext()
  }
})

// The message picked to be sent now is at the front of the queue
function onSend() {
  if (status.value === 'submitted' || status.value === 'streaming') {
    stop()
  } else {
    sendNext()
  }
}
</script>

<template>
  <UChatPrompt
    v-model="input"
    v-model:queue="queue"
    :status="status"
    :error="error"
    @submit="onSubmit"
    @send="onSend"
  >
    <UChatPromptSubmit :status="status" @stop="stop()" @reload="regenerate()" />
  </UChatPrompt>
</template>
```

After an `error`, the prompt is disabled and the queue waits until the user reloads the failed response. Stopping the response sends the next queued message right away.

> [!NOTE]
> 
> With [tool approval](https://ui.nuxt.com/docs/components/chat-tool#with-approval-flow), a response waiting for the user's answer is `ready` too. Skip `sendNext` while the last message has a part in the `approval-requested` state, a new message would leave the approval unanswered.

## API

### Props

```ts
/**
 * Props for the ChatPrompt component
 */
interface ChatPromptProps {
  /**
   * The element or component this component should render as.
   * @default 'form'
   */
  as?: any;
  /**
   * The placeholder text for the textarea.
   * @default t('chatPrompt.placeholder')
   */
  placeholder?: string | undefined;
  /**
   * @default 'primary'
   */
  color?: "error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral" | undefined;
  /**
   * @default 'outline'
   */
  variant?: "outline" | "soft" | "subtle" | "naked" | undefined;
  /**
   * When `true`, pressing `Enter` submits and `Shift+Enter` inserts a newline.
   * When `false`, pressing `Enter` inserts a newline and `Ctrl+Enter` / `Cmd+Enter` submits.
   * @default true
   */
  submitOnEnter?: boolean | undefined;
  /**
   * The status of the chat. Submitting while it is `submitted` or `streaming` is ignored, or queued with `v-model:queue`.
   */
  status?: "error" | "submitted" | "streaming" | "ready" | undefined;
  error?: Error | undefined;
  /**
   * The file types accepted when pasting or dropping files, used with `v-model:files`.
   * @default '*'
   */
  accept?: string | undefined;
  /**
   * Allow more than one file, used with `v-model:files`.
   * @default true
   */
  multiple?: boolean | undefined;
  /**
   * The props of the attachments displayed above the textarea, used with `v-model:files`.
   * `{ removable: true }`{lang="ts-type"}
   */
  attachment?: Pick<ChatAttachmentProps, "icon" | "variant" | "class" | "ui" | "size" | "orientation" | "removable" | "removeIcon"> | undefined;
  /**
   * The icon displayed to collapse the queue, used with `v-model:queue`.
   * @default appConfig.ui.icons.chevronDown
   */
  queueIcon?: any;
  ui?: { root?: SlotClass; header?: SlotClass; files?: SlotClass; queue?: SlotClass; queueTrigger?: SlotClass; queueTriggerIcon?: SlotClass; queueCount?: SlotClass; queueList?: SlotClass; queueItem?: SlotClass; queueItemLeading?: SlotClass; queueItemLabel?: SlotClass; queueItemActions?: SlotClass; body?: SlotClass; footer?: SlotClass; base?: SlotClass; } & { root?: SlotClass; base?: SlotClass; leading?: SlotClass; leadingIcon?: SlotClass; leadingAvatar?: SlotClass; leadingAvatarSize?: SlotClass; trailing?: SlotClass; trailingIcon?: SlotClass; } | undefined;
  /**
   * @default 1
   */
  rows?: number | undefined;
  /**
   * @default true
   */
  autofocus?: boolean | undefined;
  autofocusDelay?: number | undefined;
  /**
   * @default true
   */
  autoresize?: boolean | undefined;
  autoresizeDelay?: number | undefined;
  maxrows?: number | undefined;
  /**
   * Display an icon based on the `leading` and `trailing` props.
   */
  icon?: any;
  /**
   * Display an avatar on the left side.
   */
  avatar?: AvatarProps | undefined;
  /**
   * When `true`, the loading icon will be displayed.
   */
  loading?: boolean | undefined;
  /**
   * The icon when the `loading` prop is `true`.
   * @default appConfig.ui.icons.loading
   */
  loadingIcon?: any;
  disabled?: boolean | undefined;
  /**
   * @default ''
   */
  modelValue?: string | undefined;
  files?: File[] | undefined;
  queue?: ChatPromptQueueItem[] | undefined;
}
```

> [!NOTE]
> See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attributes
> 
> This component also supports all native `<textarea>` HTML attributes.

### Slots

```ts
/**
 * Slots for the ChatPrompt component
 */
interface ChatPromptSlots {
  header(): any;
  /**
   * Replace the queued messages rendered above the textarea when using `v-model:queue`.
   */
  queue(): any;
  /**
   * Replace the attachments rendered above the textarea when using `v-model:files`.
   */
  files(): any;
  footer(): any;
  /**
   * Replace the internal textarea, e.g. to render an [Editor](/docs/components/editor) with mentions.
   */
  body(): any;
  leading(): any;
  default(): any;
  trailing(): any;
}
```

### Emits

```ts
/**
 * Emitted events for the ChatPrompt component
 */
interface ChatPromptEmits {
  close: (payload: [event: Event]) => void;
  submit: (payload: [event: Event]) => void;
  update:modelValue: (payload: [value: string]) => void;
  send: (payload: [item: ChatPromptQueueItem]) => void;
  update:files: (payload: [value: File[] | undefined]) => void;
  update:queue: (payload: [value: ChatPromptQueueItem[] | undefined]) => void;
}
```

### Expose

When accessing the component via a template ref, you can use the following:

| Name | Type |
| --- | --- |
| `textareaRef` | `Ref<HTMLTextAreaElement \| null>` |
| `open()` | `void` |

## Theme

```ts [app.config.ts]
export default defineAppConfig({
  ui: {
    chatPrompt: {
      slots: {
        root: 'relative flex flex-col items-stretch gap-2 px-2.5 py-2 w-full rounded-lg isolate before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-sm mt-(--chat-prompt-queue-height)',
        header: 'flex items-center gap-1.5',
        files: 'flex flex-wrap items-center gap-1.5 mt-0.5',
        queue: 'absolute bottom-full inset-x-[max(--spacing(2.5),calc(var(--ui-radius)*2))] z-10 flex flex-col px-1 rounded-t-lg border border-b-0 border-default bg-elevated/50 backdrop-blur-sm overflow-hidden',
        queueTrigger: 'flex items-center gap-1.5 w-full p-1.5 text-xs font-medium text-muted hover:text-default focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-inverted rounded-md',
        queueTriggerIcon: [
          'shrink-0 size-4 data-[state=open]:rotate-180',
          'transition-transform duration-200 ease-out motion-reduce:transition-none'
        ],
        queueCount: 'text-dimmed tabular-nums',
        queueList: 'flex flex-col max-h-60 overflow-y-auto pb-1',
        queueItem: 'group/queue flex items-center gap-2 h-7 ps-2 pe-0.5 rounded-sm hover:bg-elevated',
        queueItemLeading: 'shrink-0 size-3 rounded-full ring ring-accented',
        queueItemLabel: 'flex-1 min-w-0 truncate text-sm text-default',
        queueItemActions: [
          'flex items-center shrink-0 [@media(hover:hover)]:opacity-0 group-hover/queue:opacity-100 group-focus-within/queue:opacity-100',
          'transition-opacity ease-out'
        ],
        body: 'items-start gap-1.5',
        footer: 'flex items-center justify-between gap-1.5',
        base: 'px-0 rounded-none'
      },
      variants: {
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        variant: {
          outline: {
            root: 'bg-default/75 ring ring-default',
            queue: 'bottom-[calc(100%+1px)]'
          },
          soft: {
            root: 'bg-elevated/50',
            queue: 'border-0'
          },
          subtle: {
            root: 'bg-elevated/50 ring ring-default',
            queue: 'bottom-[calc(100%+1px)]'
          },
          naked: {
            root: ''
          }
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-primary/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-primary has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-primary'
          }
        },
        {
          color: 'secondary',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-secondary/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-secondary has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-secondary'
          }
        },
        {
          color: 'success',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-success/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-success has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-success'
          }
        },
        {
          color: 'info',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-info/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-info has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-info'
          }
        },
        {
          color: 'warning',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-warning/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-warning has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-warning'
          }
        },
        {
          color: 'error',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-error/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-error has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-error'
          }
        },
        {
          color: 'primary',
          variant: 'soft',
          class: {
            root: 'outline-primary/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'secondary',
          variant: 'soft',
          class: {
            root: 'outline-secondary/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'success',
          variant: 'soft',
          class: {
            root: 'outline-success/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'info',
          variant: 'soft',
          class: {
            root: 'outline-info/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'warning',
          variant: 'soft',
          class: {
            root: 'outline-warning/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'error',
          variant: 'soft',
          class: {
            root: 'outline-error/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        },
        {
          color: 'neutral',
          variant: [
            'outline',
            'subtle'
          ],
          class: {
            root: 'outline-inverted/25 has-[textarea:focus-visible]:outline-3 has-[textarea:focus-visible]:ring-inverted has-[[contenteditable]:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:ring-inverted'
          }
        },
        {
          color: 'neutral',
          variant: 'soft',
          class: {
            root: 'outline-inverted/25 has-[textarea:focus-visible]:outline-3 has-[[contenteditable]:focus-visible]:outline-3'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        variant: 'outline'
      }
    }
  }
})
```

## Changelog

See commit history for [component](https://github.com/nuxt/ui/commits/v4/src/runtime/components/ChatPrompt.vue) and [theme](https://github.com/nuxt/ui/commits/v4/src/theme/chat-prompt.ts).

---

- [Textarea](https://ui.nuxt.com/docs/components/textarea)
- [GitHub](https://github.com/nuxt/ui/blob/v4/src/runtime/components/ChatPrompt.vue)


## Sitemap

See the full [sitemap](https://ui.nuxt.com/sitemap.md) for all pages.
