Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new

Suggestion

A row of suggested prompts that can start a conversation with one selection.

Interactive preview

The shared preview uses React Base. The source below uses Reka UI. Each example runs with local data.

Installation

Complete project setup with vue/reka, then install these components:

pnpm exec frontfriend download button message bubble input-group icon -f vue --primitive reka

The row scrolls horizontally when its buttons do not fit. Buttons stay in the tab order, and focus brings each one into view. Connect the selected prompt to your send action.

Usage

Complete example

<script setup lang="ts">
import { ref } from "vue";
import { Message } from "@/components/ui/message";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { Button } from "@/components/ui/button";
import { InputGroup } from "@/components/ui/input-group";
import { InputGroupTextarea } from "@/components/ui/input-group";
import { InputGroupAddon } from "@/components/ui/input-group";
import { InputGroupButton } from "@/components/ui/input-group";
import { Icon } from "@/components/ui/icon";

const args = {
  "disabled": false,
};
const prompt = ref("");
const text = ref("");
const status = ref("");
const fileInput = ref<HTMLInputElement | null>(null);
const suggestions = [
  "Add a dark mode toggle",
  "Check the form",
  "Explain this change",
  "Write a test",
];
function send(value: string) {
  if (args.disabled || !value.trim()) return;
  prompt.value = value.trim();
  text.value = "";
  status.value = "Reply added.";
}
function keydown(event: KeyboardEvent) {
  if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
    event.preventDefault();
    send(text.value);
  }
}
function selectFiles(event: Event) {
  const input = event.target as HTMLInputElement;
  const count = input.files?.length ?? 0;
  status.value = `${count} ${count === 1 ? "file" : "files"} attached.`;
  input.value = "";
}
</script>

<template>
  <div
    style="
      width: 100%;
      max-width: 560px;
      min-width: 0;
      margin-inline: auto;
      display: flex;
      flex-direction: column;
      gap: 24px;
      padding-block: 16px;
      font-size: var(--text-sm);
      line-height: 1.5;
    "
  >
    <div v-if="prompt" style="display: grid; gap: 16px">
      <Message align="end"
        ><MessageContent
          ><Bubble align="end" variant="muted"
            ><BubbleContent>{{ prompt }}</BubbleContent></Bubble
          ></MessageContent
        ></Message
      ><Message
        ><MessageContent
          ><p>
            I will help you with “{{ prompt }}”. Start with a small change, then
            check the result.
          </p></MessageContent
        ></Message
      >
    </div>
    <div style="display: grid; gap: 8px">
      <div
        role="group"
        aria-label="Suggested prompts"
        style="display: flex; gap: 8px; overflow-x: auto; padding-block: 4px"
      >
        <span
          v-for="suggestion in suggestions"
          :key="suggestion"
          style="flex-shrink: 0"
          ><Button
            variant="outline"
            size="sm"
            :disabled="args.disabled"
            @click="send(suggestion)"
            >{{ suggestion }}</Button
          ></span
        >
      </div>
      <form @submit.prevent="send(text)">
        <input
          ref="fileInput"
          type="file"
          multiple
          hidden
          aria-label="Attach files"
          @change="selectFiles"
        /><InputGroup
          ><InputGroupTextarea
            v-model="text"
            aria-label="Prompt"
            placeholder="Ask the agent to change your code..."
            :disabled="args.disabled"
            @keydown="keydown" /><InputGroupAddon align="block-end"
            ><InputGroupButton
              type="button"
              size="icon-sm"
              variant="outline"
              aria-label="Attach files"
              :disabled="args.disabled"
              @click="fileInput?.click()"
              ><Icon name="Plus" /></InputGroupButton
            ><span style="flex: 1" /><InputGroupButton
              type="submit"
              size="icon-sm"
              :variant="text.trim() && !args.disabled ? 'default' : 'secondary'"
              aria-label="Send"
              :aria-disabled="args.disabled || !text.trim()"
              ><Icon name="ArrowUp" /></InputGroupButton></InputGroupAddon
        ></InputGroup>
      </form>
    </div>
    <p
      role="status"
      style="
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
      "
    >
      {{ status }}
    </p>
  </div>
</template>

Components and API

This composition has no separate component API. Use the APIs for its installed components.

ComponentTarget reference
button Reka UI API
message Reka UI API
bubble Reka UI API
input-group Reka UI API
icon Reka UI API