# Suggestions



import SuggestionDefault from "@/components/nexus-ui/examples/suggestion/default";
import SuggestionVertical from "@/components/nexus-ui/examples/suggestion/vertical";
import SuggestionCustomValue from "@/components/nexus-ui/examples/suggestion/custom-value";
import SuggestionWithIcons from "@/components/nexus-ui/examples/suggestion/with-icons";
import SuggestionWithPromptInput from "@/components/nexus-ui/examples/suggestion/with-prompt-input";
import SuggestionVariants from "@/components/nexus-ui/examples/suggestion/variants";
import SuggestionWithPanel from "@/components/nexus-ui/examples/suggestion/with-panel";

Clickable prompt suggestion chips that guide users toward common queries. Built on shadcn's `Button` with a composable compound pattern.

<DemoWithCode src="components/nexus-ui/examples/suggestion/default.tsx">
  <SuggestionDefault />
</DemoWithCode>

Installation [#installation]

<Tabs items={["CLI", "Manual"]} framed={false}>
  <Tab value="CLI">
    <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
      <Tab value="npm">
        ```bash
        npx shadcn@latest add @nexus-ui/suggestions
        ```
      </Tab>

      <Tab value="pnpm">
        ```bash
        pnpm dlx shadcn@latest add @nexus-ui/suggestions
        ```
      </Tab>

      <Tab value="yarn">
        ```bash
        yarn dlx shadcn@latest add @nexus-ui/suggestions
        ```
      </Tab>

      <Tab value="bun">
        ```bash
        bunx shadcn@latest add @nexus-ui/suggestions
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab value="Manual">
    <Steps>
      <Step>
        <h3>
          Install the following dependencies:
        </h3>

        <Tabs items={["npm", "pnpm", "yarn", "bun"]}>
          <Tab value="npm">
            ```bash
            npx shadcn@latest add button && npm install @radix-ui/react-presence @radix-ui/react-slot class-variance-authority
            ```
          </Tab>

          <Tab value="pnpm">
            ```bash
            pnpm dlx shadcn@latest add button && pnpm add @radix-ui/react-presence @radix-ui/react-slot class-variance-authority
            ```
          </Tab>

          <Tab value="yarn">
            ```bash
            yarn dlx shadcn@latest add button && yarn add @radix-ui/react-presence @radix-ui/react-slot class-variance-authority
            ```
          </Tab>

          <Tab value="bun">
            ```bash
            bunx shadcn@latest add button && bun add @radix-ui/react-presence @radix-ui/react-slot class-variance-authority
            ```
          </Tab>
        </Tabs>
      </Step>

      <Step>
        <h3>
          Copy and paste the following code into your project.
        </h3>

        <ComponentSource src="components/nexus-ui/suggestions.tsx" title="components/nexus-ui/suggestions.tsx" />
      </Step>

      <Step>
        <h3>
          Update import paths to match your project setup.
        </h3>
      </Step>
    </Steps>
  </Tab>
</Tabs>

Usage [#usage]

```tsx keepBackground
import {
  Suggestions,
  SuggestionList,
  Suggestion,
} from "@/components/nexus-ui/suggestions";
```

```tsx keepBackground
<Suggestions onSelect={(value) => handleSuggestion(value)}>
  <SuggestionList>
    <Suggestion>Tell me a joke</Suggestion>
    <Suggestion>Explain quantum computing</Suggestion>
  </SuggestionList>
</Suggestions>
```

Examples [#examples]

Variants [#variants]

The `Suggestion` component supports three variants: `filled` (filled background), `outline` (bordered), and `ghost` (transparent until hovered).

<DemoWithCode src="components/nexus-ui/examples/suggestion/variants.tsx">
  <SuggestionVariants />
</DemoWithCode>

Vertical Layout [#vertical-layout]

Use `orientation="vertical"` on `SuggestionList` to stack suggestions in a column.

<DemoWithCode src="components/nexus-ui/examples/suggestion/vertical.tsx">
  <SuggestionVertical />
</DemoWithCode>

With Custom Value [#with-custom-value]

Use the `value` prop when the display text differs from the value passed to `onSelect`.

<DemoWithCode src="components/nexus-ui/examples/suggestion/custom-value.tsx">
  <SuggestionCustomValue />
</DemoWithCode>

With Icons [#with-icons]

Since `Suggestion` renders a shadcn `Button`, you can add icons alongside text.

<DemoWithCode src="components/nexus-ui/examples/suggestion/with-icons.tsx">
  <SuggestionWithIcons />
</DemoWithCode>

With Prompt Input [#with-prompt-input]

Clicking a suggestion populates the `PromptInput` textarea, combining both components.

<DemoWithCode src="components/nexus-ui/examples/suggestion/with-prompt-input.tsx">
  <SuggestionWithPromptInput />
</DemoWithCode>

With Panel [#with-panel]

Category chips that open a panel with related suggestions. Uses the `highlight` prop to style matching terms.

<DemoWithCode src="components/nexus-ui/examples/suggestion/with-panel.tsx" previewClassName="h-[600px]">
  <SuggestionWithPanel />
</DemoWithCode>

Vercel AI SDK Integration [#vercel-ai-sdk-integration]

Combine Suggestions with [Prompt Input](/docs/components/prompt-input) and the [Vercel AI SDK](https://sdk.vercel.ai) for a chat interface with quick-start prompts.

<Steps>
  <Step>
    <h3>
      Install the AI SDK
    </h3>

    ```bash
    npm install ai @ai-sdk/react @ai-sdk/openai
    ```
  </Step>

  <Step>
    <h3>
      Create your chat API route
    </h3>

    See [Prompt Input docs](/docs/components/prompt-input#vercel-ai-sdk-integration) for the route implementation.
  </Step>

  <Step>
    <h3>
      Wire Suggestions + Prompt Input to 

      `useChat`
    </h3>

    ```tsx
    "use client";

    import { useState } from "react";
    import { useChat } from "@ai-sdk/react";
    import { DefaultChatTransport } from "ai";
    import { Button } from "@/components/ui/button";
    import {
      PromptInput,
      PromptInputActions,
      PromptInputAction,
      PromptInputActionGroup,
      PromptInputTextarea,
    } from "@/components/nexus-ui/prompt-input";
    import {
      Suggestions,
      SuggestionList,
      Suggestion,
    } from "@/components/nexus-ui/suggestions";
    import {
      ArrowUp02Icon,
      PlusSignIcon,
      SquareIcon,
    } from "@hugeicons/core-free-icons";
    import { HugeiconsIcon } from "@hugeicons/react";

    export default function ChatWithSuggestions() {
      const { sendMessage, status } = useChat({
        transport: new DefaultChatTransport({ api: "/api/chat" }),
      });
      const [input, setInput] = useState("");
      const isLoading = status !== "ready";

      const handleSubmit = (e?: React.FormEvent) => {
        e?.preventDefault();
        if (input.trim()) {
          sendMessage({ text: input });
          setInput("");
        }
      };

      return (
        <div className="flex w-full flex-col gap-6">
          <form onSubmit={handleSubmit} className="w-full">
            <PromptInput onSubmit={handleSubmit}>
              <PromptInputTextarea
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Ask anything..."
                disabled={isLoading}
              />
              <PromptInputActions>
                <PromptInputActionGroup>
                  <PromptInputAction asChild>
                    <Button
                      type="button"
                      variant="ghost"
                      size="icon-sm"
                      className="cursor-pointer rounded-full text-secondary-foreground active:scale-97 disabled:opacity-70 hover:dark:bg-secondary"
                    >
                      <HugeiconsIcon icon={PlusSignIcon} strokeWidth={2.0} className="size-4" />
                    </Button>
                  </PromptInputAction>
                </PromptInputActionGroup>
                <PromptInputActionGroup>
                  <PromptInputAction asChild>
                    <Button
                      type="submit"
                      size="icon-sm"
                      className="cursor-pointer rounded-full active:scale-97 disabled:opacity-70"
                      disabled={isLoading || !input.trim()}
                    >
                      {isLoading ? (
                        <HugeiconsIcon icon={SquareIcon} strokeWidth={2.0} className="size-3.5 fill-current" />
                      ) : (
                        <HugeiconsIcon icon={ArrowUp02Icon} strokeWidth={2.0} className="size-4" />
                      )}
                    </Button>
                  </PromptInputAction>
                </PromptInputActionGroup>
              </PromptInputActions>
            </PromptInput>
          </form>

          <Suggestions onSelect={(value) => setInput(value)}>
            <SuggestionList className="justify-center">
              <Suggestion>What is AI?</Suggestion>
              <Suggestion>Teach me Engineering from scratch</Suggestion>
              <Suggestion>How to learn React?</Suggestion>
              <Suggestion>Design a weekly workout plan</Suggestion>
              <Suggestion>Places to visit in France</Suggestion>
            </SuggestionList>
          </Suggestions>
        </div>
      );
    }
    ```

    For one-click submission (suggestion sends immediately without editing), call `sendMessage` in `onSelect`:

    ```tsx
    <Suggestions
      onSelect={(value) => {
        if (value.trim()) {
          sendMessage({ text: value });
          setInput("");
        }
      }}
    >
    ```
  </Step>
</Steps>

API Reference [#api-reference]

Suggestions [#suggestions]

The root container that provides `onSelect` context to all child `Suggestion` components. Extends `React.HTMLAttributes<HTMLDivElement>`.

<TypeTable
  type={{
  onSelect: {
    type: "(value: string) => void",
    description:
      "Callback fired when any Suggestion is clicked. Receives the suggestion's value or text content.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the root container.",
  },
}}
/>

SuggestionList [#suggestionlist]

Layout wrapper for arranging suggestions horizontally or vertically. Extends `React.HTMLAttributes<HTMLDivElement>`.

<TypeTable
  type={{
  orientation: {
    type: '"horizontal" | "vertical"',
    default: '"horizontal"',
    description:
      "Layout direction. Horizontal wraps items in a row, vertical stacks them in a column.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the list container.",
  },
}}
/>

Suggestion [#suggestion]

A clickable pill that triggers `onSelect` from the parent `Suggestions` context. Renders as a shadcn `Button`. Extends `Button` props (except `variant`).

<TypeTable
  type={{
  variant: {
    type: '"filled" | "outline" | "ghost"',
    default: '"filled"',
    description: "Visual style of the suggestion pill.",
  },
  highlight: {
    type: "string | string[]",
    description:
      "Defines the text(s) or word(s) in the suggestion to be highlighted. Matches are case-insensitive. Style the highlighted text via className or by composing your own wrapper.",
  },
  value: {
    type: "string",
    description: "The value passed to onSelect. Defaults to children when children is a string.",
  },
  children: {
    type: "React.ReactNode",
    description: "The content to display. Used as the value for onSelect when value is not provided.",
  },
  onClick: {
    type: "React.MouseEventHandler<HTMLButtonElement>",
    description: "Optional click handler. Called before onSelect.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the button.",
  },
}}
/>

SuggestionPanel [#suggestionpanel]

Full-width panel that displays below the input, covering the category pills. Uses `Presence` for enter/exit animations. Closes on Escape via `onOpenChange`. Use with `SuggestionPanelHeader`, `SuggestionPanelTitle`, `SuggestionPanelClose`, and `SuggestionPanelContent`.

<TypeTable
  type={{
  open: {
    type: "boolean",
    default: "true",
    description: "Controls panel visibility. Use for controlled open/close state.",
  },
  onOpenChange: {
    type: "(open: boolean) => void",
    description:
      "Callback fired when the panel open state changes (e.g. Escape key, SuggestionPanelClose click).",
  },
  onClose: {
    type: "() => void",
    description:
      "Callback fired when the panel's exit animation completes. Use for cleanup (e.g. focus management).",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the panel container.",
  },
  ref: {
    type: "React.Ref<HTMLDivElement>",
    description: "Forwarded ref to the panel div element.",
  },
}}
/>

SuggestionPanelHeader [#suggestionpanelheader]

Header row for the panel. Typically contains `SuggestionPanelTitle` and `SuggestionPanelClose`. Extends `React.HTMLAttributes<HTMLDivElement>`.

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the header.",
  },
}}
/>

SuggestionPanelTitle [#suggestionpaneltitle]

Title area for the panel header. Use for the category icon and label. Extends `React.HTMLAttributes<HTMLDivElement>`.

<TypeTable
  type={{
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the title container.",
  },
}}
/>

SuggestionPanelClose [#suggestionpanelclose]

Close button for the panel. Clicking it calls `onOpenChange(false)` via context. Use `asChild` to merge props onto a child element. Extends `React.ButtonHTMLAttributes<HTMLButtonElement>`.

<TypeTable
  type={{
  asChild: {
    type: "boolean",
    default: "false",
    description:
      "Merges props onto the child element instead of rendering a button.",
  },
  onClick: {
    type: "React.MouseEventHandler<HTMLButtonElement>",
    description: "Optional click handler. Called after the panel close is triggered.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the close button.",
  },
}}
/>

SuggestionPanelContent [#suggestionpanelcontent]

Content wrapper for the panel's suggestion list. Use with `Suggestions` and `SuggestionList` inside. Use `asChild` to merge props onto a child element. Extends `React.HTMLAttributes<HTMLDivElement>`.

<TypeTable
  type={{
  asChild: {
    type: "boolean",
    default: "false",
    description:
      "Merges props onto the child element instead of rendering a div.",
  },
  className: {
    type: "string",
    description: "Additional CSS classes to apply to the content container.",
  },
}}
/>
