Skip to content

Ask AI Button Pattern ​

The "Ask AI" button pattern lets users explicitly trigger AI assistance for a specific UI element, rather than relying on passive hover or focus tracking. It's the most reliable way to ensure the AI always gets exactly the right context.

How it works ​

  1. User sees a data element (chart, table row, KPI card)
  2. User clicks "Ask AI ✦" on that element
  3. ctx.select(element) captures the element's context
  4. Chat panel opens with the context already set
  5. User asks a question — the LLM knows exactly what they're referring to

React ​

tsx
'use client';
import { useRef, useState } from 'react';
import { Askable, useAskable } from '@askable-ui/react';

function MetricCard({ data }: { data: MetricData }) {
  const { ctx, promptContext } = useAskable();
  const cardRef = useRef<HTMLDivElement>(null);
  const [chatOpen, setChatOpen] = useState(false);

  function handleAskAI() {
    ctx.select(cardRef.current!);
    setChatOpen(true);
  }

  return (
    <>
      <Askable meta={data} ref={cardRef} className="metric-card">
        <h3>{data.label}</h3>
        <p className="value">{data.value}</p>
        <p className="delta">{data.delta}</p>

        <button className="ask-ai-btn" onClick={handleAskAI}>
          Ask AI ✦
        </button>
      </Askable>

      {chatOpen && (
        <ChatPanel
          initialContext={promptContext}
          onClose={() => setChatOpen(false)}
        />
      )}
    </>
  );
}

Vue ​

vue
<script setup lang="ts">
import { ref } from 'vue';
import { Askable, useAskable } from '@askable-ui/vue';

const props = defineProps<{ data: MetricData }>();
const { ctx } = useAskable();
const card = ref<HTMLElement | null>(null);
const chatOpen = ref(false);

function handleAskAI() {
  ctx.select(card.value!);
  chatOpen.value = true;
}
</script>

<template>
  <Askable ref="card" :meta="data">
    <h3>{{ data.label }}</h3>
    <p>{{ data.value }}</p>
    <button @click="handleAskAI">Ask AI ✦</button>
  </Askable>

  <ChatPanel v-if="chatOpen" @close="chatOpen = false" />
</template>

Svelte ​

svelte
<script lang="ts">
  import { onDestroy } from 'svelte';
  import { createAskableStore } from '@askable-ui/svelte';
  import Askable from '@askable-ui/svelte/Askable.svelte';

  export let data: MetricData;

  const { ctx, promptContext, destroy } = createAskableStore();
  onDestroy(destroy);

  let cardEl: HTMLElement;
  let chatOpen = false;

  function handleAskAI() {
    ctx.select(cardEl);
    chatOpen = true;
  }
</script>

<Askable bind:el={cardEl} meta={data}>
  <h3>{data.label}</h3>
  <p>{data.value}</p>
  <button on:click={handleAskAI}>Ask AI ✦</button>
</Askable>

{#if chatOpen}
  <ChatPanel on:close={() => chatOpen = false} />
{/if}

Multiple cards, one chat panel ​

A common pattern: many cards on a dashboard, one shared chat panel. Whichever card's button was last clicked becomes the context.

tsx
function Dashboard({ metrics }) {
  const { ctx, promptContext } = useAskable();
  const [chatOpen, setChatOpen] = useState(false);
  const refs = useRef<Record<string, HTMLDivElement | null>>({});

  return (
    <div className="dashboard">
      {metrics.map((metric) => (
        <Askable
          key={metric.id}
          meta={metric}
          ref={(el) => { refs.current[metric.id] = el; }}
        >
          <MetricChart data={metric} />
          <button
            onClick={() => {
              ctx.select(refs.current[metric.id]!);
              setChatOpen(true);
            }}
          >
            Ask AI ✦
          </button>
        </Askable>
      ))}

      {chatOpen && (
        <ChatPanel
          context={promptContext}
          onClose={() => {
            setChatOpen(false);
            ctx.clear(); // reset focus when panel closes
          }}
        />
      )}
    </div>
  );
}

Sending context to the AI ​

In your chat API handler, receive and use the context:

ts
// app/api/chat/route.ts
export async function POST(req: Request) {
  const { messages, uiContext } = await req.json();

  const system = [
    'You are a helpful data analyst assistant.',
    uiContext && `The user is asking about: ${uiContext}`,
  ].filter(Boolean).join('\n');

  // pass system + messages to your LLM...
}
tsx
// In your ChatPanel component
function ChatPanel({ context, onClose }) {
  async function sendMessage(text: string) {
    await fetch('/api/chat', {
      method: 'POST',
      body: JSON.stringify({
        messages: [...history, { role: 'user', content: text }],
        uiContext: context,
      }),
    });
  }
}

Released under the MIT License.