Give any UI element LLM awareness with one attribute.
Your LLM doesn't know what the user is looking at — so when they click a chart and ask "why is this dropping?", it guesses.
<div data-askable='{"chart":"revenue","delta":"-12%","period":"Q3"}'>
<RevenueChart />
</div>That's it. The same data that renders your component becomes the AI's context. No duplication, no custom events, no framework lock-in.
1. ANNOTATE → data-askable on any element that carries meaning
2. OBSERVE → askable.observe(document) — one call, covers everything
3. INJECT → askable.toPromptContext() — drop into any LLM call
// Before askable
LLM receives: "why is this dropping?"
LLM answers: "Revenue can decline due to many factors such as..."
// After askable — same question, radically better answer
LLM receives: "UI context: metric: revenue, delta: -12%, period: Q3
Question: why is this dropping?"
LLM answers: "Your Q3 revenue fell 12%. Based on the data you're
viewing, the most likely cause is..."npm install @askable-ui/core # zero deps, ~1kb gz
npm install @askable-ui/react # React 17+
npm install @askable-ui/vue # Vue 3
npm install @askable-ui/svelte # Svelte 4
pip install askable-django # Django 4+
pip install askable-streamlit # Streamlitimport { createAskableContext } from '@askable-ui/core';
const askable = createAskableContext();
askable.observe(document);
// In your AI handler — one line
const prompt = askable.toPromptContext();
// → "User is focused on: chart: revenue, delta: -12%, period: Q3"import { Askable, useAskable } from '@askable-ui/react';
function Dashboard() {
const { data } = useSWR('/api/metrics');
return (
<Askable meta={data.revenue}> {/* ← same data renders the chart */}
<RevenueChart data={data.revenue} /> {/* ← AND feeds the AI */}
</Askable>
);
}
function AIInput() {
const { promptContext } = useAskable({ events: ['click'] });
return (
<input
placeholder="Ask about what you're looking at…"
onKeyDown={e => {
if (e.key === 'Enter') sendToLLM(promptContext, e.currentTarget.value);
}}
/>
);
}function RevenueCard({ data }) {
const { askable } = useAskable();
const ref = useRef<HTMLDivElement>(null);
return (
<Askable meta={data} ref={ref}>
<RevenueChart data={data} />
<button onClick={() => { askable.select(ref.current!); openChat(); }}>
✦ Ask AI
</button>
</Askable>
);
}<script setup>
import { Askable, useAskable } from '@askable-ui/vue';
const { promptContext } = useAskable({ events: ['click', 'focus'] });
</script>
<template>
<Askable :meta="{ chart: 'revenue', period: 'Q3' }">
<RevenueChart />
</Askable>
<AIChatInput :context="promptContext" />
</template>{% load askable_tags %}
{% askable meta=chart_meta %}
<canvas id="revenue-chart"></canvas>
{% endaskable %}# views.py
def ai_chat(request):
data = json.loads(request.body)
return JsonResponse({
'answer': llm.chat(
system=f"UI context: {data['context']}",
user=data['question'],
)
})// Vercel AI SDK
system: `You are a helpful assistant.\n\n${askable.toPromptContext()}`
// OpenAI
{ role: 'system', content: `UI context:\n${askable.toPromptContext()}` }
// Anthropic
system: `UI context:\n${askable.toPromptContext()}`You could. You'd also write your own router.
| DIY wiring | askable | |
|---|---|---|
| Setup per component | Custom event + serializer | data-askable |
| Dynamic elements | Manual re-wire | MutationObserver built-in |
| Framework lock-in | Re-implement per stack | One core, thin adapters |
| Bundle cost | Your code + your bugs | ~1kb gz, zero deps |
| Python support | Build it yourself | Django + Streamlit ready |
| Package | Description | Size |
|---|---|---|
@askable-ui/core |
Framework-agnostic observer + context | ~1kb gz |
@askable-ui/react |
<Askable> + useAskable() |
~0.5kb gz |
@askable-ui/vue |
<Askable> + useAskable() |
~0.5kb gz |
@askable-ui/svelte |
<Askable> + createAskableStore() |
~0.5kb gz |
askable-django |
Template tags + auto-inject middleware | — |
askable-streamlit |
Returns focus as Python dict | — |
Returns a new context instance.
Start watching el. Tracks click, hover, and focus on all [data-askable] elements — including ones added dynamically.
askable.observe(document) // all triggers
askable.observe(document, { events: ['click'] }) // click only
askable.observe(document, { events: ['hover'] }) // hover only
askable.observe(document, { events: ['focus'] }) // focus onlyProgrammatically set focus — use for "Ask AI" buttons.
Returns a natural language string ready for any system prompt:
User is focused on: chart: revenue, period: Q3 — value "Q3 Revenue: $2.3M"
Returns { meta, text, element, timestamp } or null.
Subscribe to focus changes.
Remove all listeners. Call on unmount.
MIT
