10.7k

Suggestion

PreviousNext

A short prompt you can send to the agent in one click.

<script setup lang="ts">
import { ArrowUpIcon, PlusIcon } from '@lucide/vue'
import { computed, ref } from 'vue'
import { Bubble, BubbleContent } from '@/components/ui/bubble'
import { Button } from '@/components/ui/button'
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupTextarea,
} from '@/components/ui/input-group'
import { Message, MessageContent } from '@/components/ui/message'

const suggestions = [
  'Add a dark mode toggle',
  'Fix the failing build',
  'Write tests for the form',
  'Upgrade to Nuxt 4',
  'Add a loading state',
  'Find unused dependencies',
]

const replies: Record<string, string> = {
  'Add a dark mode toggle':
    'Added a ModeToggle to the site header. It calls useColorMode from @vueuse/core.',
  'Fix the failing build':
    'The build failed on a missing import in pages/index.vue. I added it and the build passes.',
  'Write tests for the form':
    'Added four tests in SignupForm.test.ts. They cover the empty, invalid and valid states.',
  'Upgrade to Nuxt 4':
    'Ran the codemod and bumped nuxt to 4. Two pages needed the new app/ directory, so I moved them.',
  'Add a loading state':
    'Added a loading state to the dashboard page. It shows skeletons while the data loads.',
  'Find unused dependencies':
    'Three packages are not imported anywhere: lodash, moment and classnames. Should I remove them?',
}

const text = ref('')
const turn = ref<{ prompt: string, reply: string } | null>(null)
const formRef = ref<HTMLFormElement | null>(null)
const canSend = computed(() => text.value.trim().length > 0)

function send(prompt: string) {
  turn.value = {
    prompt,
    reply:
      replies[prompt] ?? 'This demo only knows the suggestions. Pick one.',
  }
  text.value = ''
}

function onSubmit() {
  if (!canSend.value)
    return

  send(text.value.trim())
}

function onKeydown(event: KeyboardEvent) {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault()
    formRef.value?.requestSubmit()
  }
}
</script>

<template>
  <div class="mx-auto flex w-full max-w-lg flex-col gap-6 py-6 text-sm">
    <div v-if="turn" class="flex flex-col gap-4">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>{{ turn.prompt }}</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent>
          <p>{{ turn.reply }}</p>
        </MessageContent>
      </Message>
    </div>
    <div class="flex flex-col gap-2">
      <div class="flex scroll-fade-x scrollbar-none gap-2 overflow-x-auto overscroll-x-contain">
        <Button
          v-for="suggestion in suggestions"
          :key="suggestion"
          variant="outline"
          size="sm"
          class="shrink-0"
          @click="send(suggestion)"
        >
          {{ suggestion }}
        </Button>
      </div>
      <form ref="formRef" @submit.prevent="onSubmit">
        <InputGroup>
          <InputGroupTextarea
            v-model="text"
            placeholder="Ask the agent to change your code..."
            class="p-3"
            @keydown="onKeydown"
          />
          <InputGroupAddon align="block-end">
            <InputGroupButton
              size="icon-sm"
              variant="outline"
              class="rounded-full"
              aria-label="Attach files"
            >
              <PlusIcon />
            </InputGroupButton>
            <InputGroupButton
              type="submit"
              size="icon-sm"
              variant="default"
              class="ml-auto rounded-full"
              :disabled="!canSend"
              aria-label="Send"
            >
              <ArrowUpIcon />
            </InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      </form>
    </div>
  </div>
</template>

Suggestion is a prompt you send without typing. Each one is a Button with the prompt as its label. Put a row of them above the Prompt Input.

Installation

Suggestion is built with the Button component.

pnpm dlx shadcn-vue@latest add button

Usage

<script setup lang="ts">
import { Button } from '@/components/ui/button'
</script>

<template>
  <div class="scrollbar-none flex scroll-fade-x gap-2 overflow-x-auto overscroll-x-contain">
    <Button
      v-for="suggestion in suggestions"
      :key="suggestion"
      variant="outline"
      size="sm"
      class="shrink-0"
      @click="send(suggestion)"
    >
      {{ suggestion }}
    </Button>
  </div>
</template>

Scrolling

The row scrolls sideways when the suggestions do not fit. scroll-fade-x fades the edge that has more to show, and shrink-0 keeps each label on one line.

AI SDK

The label is the message. Send it as is.

chat.sendMessage({ text: suggestion })

Migrating from AI Elements Vue

If you added Suggestion from AI Elements Vue, your project has a components/ai-elements/suggestion/ folder. Paste this prompt into your coding agent to rebuild it on Button. Every export keeps its name and props, so the code that uses it keeps working.

Migrate components/ai-elements/suggestion/ from AI Elements Vue to the shadcn-vue Button component.

Keep every export, with the same name and props, so the code that imports it keeps working. Only change how each part renders.

1. Install Button: npx shadcn-vue@latest add button
2. Read https://www.shadcn-vue.com/docs/ai-elements/suggestion and build Suggestion the same way.
3. Rebuild each part:
   - Suggestions renders a div with class="flex scroll-fade-x scrollbar-none gap-2 overflow-x-auto overscroll-x-contain", instead of a ScrollArea. Change its props to a class prop and pass the slot through.
   - Suggestion renders a Button with variant="outline", size="sm" and class="shrink-0". Keep the click emit, called with the suggestion.
4. Remove the imports the files no longer use.
5. Run vue-tsc and fix any errors.