ChatPrompt

An enhanced Textarea for submitting prompts in AI chat interfaces.

Usage

The ChatPrompt component renders a <form> element and extends the Textarea component so you can pass any property such as icon, placeholder, autofocus, etc.

The ChatPrompt handles the following events:
  • The form is submitted when the user presses ↵ or when the user clicks on the submit button.
  • The textarea is blurred when ⎋ is pressed and emits a close event.

Variant

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

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

Examples

These chat components are designed to be used with the AI SDK v5 from Vercel AI SDK.
Check out the source code of our AI Chat template on GitHub for a real-life example.

Within a page

Use the ChatPrompt component with the Chat class from AI SDK v5 to display a chat prompt within a page.

Pass the input prop alongside the error prop to disable the textarea when an error occurs.

pages/[id].vue
<script setup lang="ts">
import { Chat } from '@ai-sdk/vue'
import { getTextFromMessage } from '@nuxt/ui/utils/ai'

const input = ref('')

const chat = new Chat({
  onError(error) {
    console.error('Chat error:', error)
  }
})

const handleSubmit = (e: Event) => {
  e.preventDefault()
  chat.sendMessage({ text: input.value })
  input.value = ''
}
</script>

<template>
  <UDashboardPanel>
    <template #body>
      <UContainer>
        <UChatMessages :messages="chat.messages" :status="chat.status">
          <template #content="{ message }">
            <MDC :value="getTextFromMessage(message)" :cache-key="message.id" unwrap="p" />
          </template>
        </UChatMessages>
      </UContainer>
    </template>

    <template #footer>
      <UContainer>
        <UChatPrompt v-model="input" :error="chat.error" @submit="handleSubmit">
          <UChatPromptSubmit :status="chat.status" @stop="chat.stop" @reload="chat.regenerate" />
        </UChatPrompt>
      </UContainer>
    </template>
  </UDashboardPanel>
</template>

You can also use it as a starting point for a chat interface.

pages/index.vue
<script setup lang="ts">
import { Chat } from '@ai-sdk/vue'

const input = ref('')
const chat = new Chat()

async function onSubmit() {
  const userInput = input.value
  input.value = ''

  chat.sendMessage({ text: userInput })

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

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

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

API

Props

Prop Default Type
as

'form'

any

The element or component this component should render as.

placeholder

t('chatPrompt.placeholder')

string

The placeholder text for the textarea.

autofocus

true

boolean

autoresize

true

boolean

rows

1

number

variant

'outline'

"outline" | "soft" | "subtle" | "naked"

error

Error

loading

boolean

When true, the loading icon will be displayed.

loadingIcon

appConfig.ui.icons.loading

string

The icon when the loading prop is true.

icon

string

Display an icon based on the leading and trailing props.

avatar

AvatarProps

Display an avatar on the left side.

autofocusDelay

number

autoresizeDelay

number

maxrows

number

modelValue

''

string

ui

{ root?: ClassNameValue; header?: ClassNameValue; body?: ClassNameValue; footer?: ClassNameValue; base?: ClassNameValue; } & { root?: ClassNameValue; base?: ClassNameValue; leading?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; }

Slots

Slot Type
header

{}

footer

{}

leading

{}

default

{}

trailing

{}

Emits

Event Type
close

[event: Event]

submit

[event: Event]

update:modelValue

[value: string]

Theme

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 backdrop-blur',
        header: 'flex items-center gap-1.5',
        body: 'items-start',
        footer: 'flex items-center justify-between gap-1.5',
        base: 'text-base/5'
      },
      variants: {
        variant: {
          outline: {
            root: 'bg-default/75 ring ring-default'
          },
          soft: {
            root: 'bg-elevated/50'
          },
          subtle: {
            root: 'bg-elevated/50 ring ring-default'
          },
          naked: {
            root: ''
          }
        }
      },
      defaultVariants: {
        variant: 'outline'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        chatPrompt: {
          slots: {
            root: 'relative flex flex-col items-stretch gap-2 px-2.5 py-2 w-full rounded-lg backdrop-blur',
            header: 'flex items-center gap-1.5',
            body: 'items-start',
            footer: 'flex items-center justify-between gap-1.5',
            base: 'text-base/5'
          },
          variants: {
            variant: {
              outline: {
                root: 'bg-default/75 ring ring-default'
              },
              soft: {
                root: 'bg-elevated/50'
              },
              subtle: {
                root: 'bg-elevated/50 ring ring-default'
              },
              naked: {
                root: ''
              }
            }
          },
          defaultVariants: {
            variant: 'outline'
          }
        }
      }
    })
  ]
})

Changelog

No recent changes