Skip to content
AI Interfaces block

Retrieved Context Chunks

The RAG debugging surface: ranked passages with similarity as a meter, matched spans in real mark elements, and filter chips wired as a radiogroup so arrows move between them.

327 lineslucide-reactAdded 13 Aug 2026Updated 2 Sept 2026
  • rag
  • retrieval
  • chunks
  • embeddings
  • similarity

What's included

  • components/context-chunk-cards.tsx
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add context-chunk-cards

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Start a page with this section — add more, order them, and leave with the page source.

Preview

Retrieved context

4 chunks for “why did northwind not get the volume price”, ranked by similarity

  1. Document: Pricing handbook· §4.2 — Volume breaksStrong match0.910.91

    Accounts above 1,200 units per order qualify for the tier-two rate of $8.40, held for the duration of the contract year. The break is applied per order, not per quarter, so a single large order beats two smaller ones of the same total volume.

  2. Support ticket: Ticket #4192· Northwind Retail · resolvedPartial match0.780.78

    Customer asked why their November order did not get the tier-two price. Their October and November orders were 700 units each — combined they clear the break, individually they do not. Confirmed with finance that this is working as intended.

  3. Table: warehouse.orders· 2,481 rows scannedPartial match0.640.64

    Aggregated order volume by account for the trailing four quarters, filtered to accounts with more than one order in the period.

  4. Web page: supplier.example.com· Lead times · fetched todayWeak match0.410.41

    Standard lead time for the winter blend is 21 days from purchase order. Expedited shipping is available at a 15% surcharge and reduces this to 9 days.

Rendered live in your current theme — this is the same component whose source is below, not a screenshot of it.

Source

components/context-chunk-cards.tsx
/**
 * <ContextChunkCards> — the retrieved passages an answer was built from,
 * with their similarity scores and provenance.
 *
 * The debugging surface for RAG, and the trust surface for the user. When an
 * answer is wrong the first question is always "what did it actually read",
 * and this is the block that answers it.
 *
 * Details worth keeping:
 *
 *  - The matched span inside each chunk is a `<mark>`, not a coloured span.
 *    `<mark>` means "relevant to the current context" — precisely this — and
 *    is exposed as a highlight by screen readers rather than being silent.
 *  - Score is a `<meter>` with a hidden numeric label, so "0.82" is
 *    available to a reader instead of a bar it cannot see.
 *  - Chunks are ordered by score and *say* they are, because a list whose
 *    order carries meaning must declare it. The rank is rendered, not
 *    implied by position.
 *  - Long chunks clamp to four lines with a real expand button rather than a
 *    fade-out that hides text with no way to reach it.
 *  - The filter chips are a `radiogroup`, so arrow keys move between them
 *    and only the active one is in the tab order — the standard pattern for
 *    a single-choice toolbar.
 *
 * Score colouring never carries meaning alone: every card states its band in
 * text beside the meter.
 */

'use client'

import * as React from 'react'
import { ChevronDown, Database, FileText, Globe, MessageSquare } from 'lucide-react'

export type ChunkKind = 'doc' | 'table' | 'web' | 'ticket'

export interface ContextChunk {
  id: string
  kind: ChunkKind
  source: string
  location: string
  /** 0–1. */
  score: number
  text: string
  /** Substring of `text` to mark as the matched span. */
  match?: string
}

export interface ContextChunkCardsProps {
  heading?: string
  query?: string
  chunks?: ContextChunk[]
  className?: string
}

const KIND_ICON = {
  doc: FileText,
  table: Database,
  web: Globe,
  ticket: MessageSquare,
} as const

const KIND_LABEL = {
  doc: 'Document',
  table: 'Table',
  web: 'Web page',
  ticket: 'Support ticket',
} as const

const DEFAULT_CHUNKS: ContextChunk[] = [
  {
    id: '1',
    kind: 'doc',
    source: 'Pricing handbook',
    location: '§4.2 — Volume breaks',
    score: 0.91,
    text: 'Accounts above 1,200 units per order qualify for the tier-two rate of $8.40, held for the duration of the contract year. The break is applied per order, not per quarter, so a single large order beats two smaller ones of the same total volume.',
    match: 'The break is applied per order, not per quarter',
  },
  {
    id: '2',
    kind: 'ticket',
    source: 'Ticket #4192',
    location: 'Northwind Retail · resolved',
    score: 0.78,
    text: 'Customer asked why their November order did not get the tier-two price. Their October and November orders were 700 units each — combined they clear the break, individually they do not. Confirmed with finance that this is working as intended.',
    match: 'combined they clear the break, individually they do not',
  },
  {
    id: '3',
    kind: 'table',
    source: 'warehouse.orders',
    location: '2,481 rows scanned',
    score: 0.64,
    text: 'Aggregated order volume by account for the trailing four quarters, filtered to accounts with more than one order in the period.',
  },
  {
    id: '4',
    kind: 'web',
    source: 'supplier.example.com',
    location: 'Lead times · fetched today',
    score: 0.41,
    text: 'Standard lead time for the winter blend is 21 days from purchase order. Expedited shipping is available at a 15% surcharge and reduces this to 9 days.',
    match: '21 days from purchase order',
  },
]

const FILTERS = ['All', 'Documents', 'Tables', 'Web'] as const

/**
 * `<meter>` restyled while keeping its semantics.
 *
 * Flat selectors in a `<style>` block, not Tailwind utilities, for two
 * reasons that are invisible until the meter renders in platform yellow:
 * Chromium gates `::-webkit-meter-*` on the *prefixed*
 * `-webkit-appearance: none`, which `appearance-none` does not emit; and an
 * arbitrary variant nests as `:is(.cls)::-webkit-meter-bar`, which Chromium
 * rejects outright — a UA shadow pseudo-element cannot sit behind `:is()`.
 * See the fuller note in `confidence-recommendation`.
 */
const METER_CSS = `
  .hl-meter { -webkit-appearance: none; appearance: none; background: none; }
  .hl-meter::-webkit-meter-bar {
    background: var(--muted); border: none; border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-optimum-value {
    background: var(--color-emerald-500, #10b981); border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-suboptimum-value {
    background: var(--color-amber-500, #f59e0b); border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-even-less-good-value {
    background: var(--color-rose-500, #f43f5e); border-radius: 9999px;
  }
  .hl-meter::-moz-meter-bar { border-radius: 9999px; }
`

function band(score: number) {
  if (score >= 0.8) return 'Strong match'
  if (score >= 0.6) return 'Partial match'
  return 'Weak match'
}

export function ContextChunkCards({
  heading = 'Retrieved context',
  query = 'why did northwind not get the volume price',
  chunks = DEFAULT_CHUNKS,
  className = '',
}: ContextChunkCardsProps) {
  const [filter, setFilter] = React.useState<(typeof FILTERS)[number]>('All')
  const [expanded, setExpanded] = React.useState<string[]>([])

  const visible = React.useMemo(() => {
    const wanted: Record<string, ChunkKind | null> = {
      All: null,
      Documents: 'doc',
      Tables: 'table',
      Web: 'web',
    }
    const kind = wanted[filter]
    const list = kind ? chunks.filter((c) => c.kind === kind) : chunks
    // Order is meaning here, so it is enforced rather than assumed.
    return [...list].sort((a, b) => b.score - a.score)
  }, [chunks, filter])

  return (
    <div className={`mx-auto w-full max-w-2xl p-6 ${className}`}>
      {/* Declared once for every meter below, not once per card. */}
      <style>{METER_CSS}</style>

      {/* -- Header ------------------------------------------------------ */}
      <div className="mb-4 flex flex-wrap items-center gap-3">
        <div className="min-w-0 flex-auto">
          {heading ? <h3 data-stress-ignore className="text-sm font-semibold">{heading}</h3> : null}
          {/* Wraps rather than truncates: "ranked by similarity" is the
              part that explains the order, and it was the part being cut. */}
          <p className="mt-0.5 text-xs text-muted-foreground">
            {visible.length} chunks for “{query}”, ranked by similarity
          </p>
        </div>

        {/* Single-choice toolbar: arrow keys move, one stop in the tab
            order. `radiogroup` is the role that describes that. */}
        <div role="radiogroup" aria-label="Filter by source type" className="flex gap-1">
          {FILTERS.map((option) => {
            const active = option === filter
            return (
              <button
                key={option}
                type="button"
                role="radio"
                aria-checked={active}
                tabIndex={active ? 0 : -1}
                onClick={() => setFilter(option)}
                onKeyDown={(e) => {
                  if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return
                  e.preventDefault()
                  const i = FILTERS.indexOf(filter)
                  const next =
                    e.key === 'ArrowRight'
                      ? FILTERS[(i + 1) % FILTERS.length]
                      : FILTERS[(i - 1 + FILTERS.length) % FILTERS.length]
                  setFilter(next)
                }}
                className={`rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
                  active
                    ? 'border-primary bg-primary/10 text-primary'
                    : 'border-border/60 text-muted-foreground hover:bg-muted'
                }`}
              >
                {option}
              </button>
            )
          })}
        </div>
      </div>

      {/* -- Chunks ------------------------------------------------------ */}
      <ol className="space-y-3">
        {visible.map((chunk, i) => {
          const Icon = KIND_ICON[chunk.kind]
          const open = expanded.includes(chunk.id)
          const panelId = `chunk-${chunk.id}`

          return (
            <li
              key={chunk.id}
              className="overflow-hidden rounded-2xl border border-border/60 bg-card"
            >
              <div className="flex flex-wrap items-center gap-2.5 border-b border-border/60 px-4 py-2.5">
                <span
                  aria-hidden
                  className="flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-muted font-mono text-[10px] font-bold text-muted-foreground"
                >
                  {i + 1}
                </span>

                <Icon aria-hidden className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />

                <span className="min-w-0 truncate text-xs font-semibold">
                  <span className="sr-only">{KIND_LABEL[chunk.kind]}: </span>
                  {chunk.source}
                </span>

                <span className="min-w-0 shrink truncate text-xs text-muted-foreground">
                  · {chunk.location}
                </span>

                <span className="ms-auto flex shrink-0 items-center gap-2">
                  <span className="text-[11px] text-muted-foreground">{band(chunk.score)}</span>
                  <meter
                    value={chunk.score}
                    min={0}
                    max={1}
                    low={0.6}
                    high={0.8}
                    optimum={1}
                    aria-label={`Similarity ${chunk.score.toFixed(2)}`}
                    className="hl-meter h-1.5 w-12"
                  >
                    {chunk.score.toFixed(2)}
                  </meter>
                  <span className="font-mono text-[11px] tabular-nums text-muted-foreground">
                    {chunk.score.toFixed(2)}
                  </span>
                </span>
              </div>

              <div className="px-4 py-3">
                <p
                  id={panelId}
                  className={`text-xs leading-relaxed text-muted-foreground ${
                    open ? '' : 'line-clamp-4'
                  }`}
                >
                  {highlight(chunk.text, chunk.match)}
                </p>

                <button
                  type="button"
                  aria-expanded={open}
                  aria-controls={panelId}
                  onClick={() =>
                    setExpanded((list) =>
                      list.includes(chunk.id)
                        ? list.filter((x) => x !== chunk.id)
                        : [...list, chunk.id],
                    )
                  }
                  className="mt-2 inline-flex items-center gap-1 rounded-md text-xs font-medium text-primary transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                >
                  {open ? 'Show less' : 'Show full chunk'}
                  <ChevronDown
                    aria-hidden
                    className={`h-3 w-3 transition-transform ${open ? 'rotate-180' : ''}`}
                  />
                </button>
              </div>
            </li>
          )
        })}
      </ol>
    </div>
  )
}

/**
 * Wrap the matched span in `<mark>`.
 *
 * Split on the literal substring rather than building a regex from it —
 * chunk text is retrieved content, and a `(` in it would otherwise throw
 * inside `new RegExp`.
 */
function highlight(text: string, match?: string): React.ReactNode {
  if (!match) return text

  const at = text.indexOf(match)
  if (at === -1) return text

  return (
    <>
      {text.slice(0, at)}
      <mark className="rounded bg-primary/15 px-0.5 text-foreground">{match}</mark>
      {text.slice(at + match.length)}
    </>
  )
}

Before you paste

  • Styling is Tailwind utility classes on semantic tokens (bg-card, text-muted-foreground) — it inherits your theme instead of overriding it.
  • Install: npm i lucide-react
  • Every prop has a default, so it renders standalone before you wire it up.

Where it goes

Drop it at components/context-chunk-cards.tsx and import it where you need the section:

import { ContextChunkCards } from '@/components/context-chunk-cards'

Customize

2 of this block’s props are simple enough to drive from here. Change them and the block below re-renders — it is the same component whose source is above, not a mock of it. Everything else it accepts is in the table underneath.

Props

Read out of the component’s own type and signature, so this cannot drift from the source below. Every prop has a default — the component renders standalone before you pass it anything.

PropTypeDefault
headingstring'Retrieved context'
querystring'why did northwind not get the volume price'
chunksContextChunk[]DEFAULT_CHUNKS
classNamestring''

Not using React?

The same block rendered once to markup, wrapped as a file your framework compiles. Tailwind classes are framework-agnostic, so the design transfers intact — the behaviour does not.

This block is interactive. The markup below is its initial state with the event handlers stripped — you will need to re-wire the behaviour in your framework.

context-chunk-cards.html
<!--
  Retrieved Context Chunks — markup from the Hoverlab catalog.

  This is the block rendered once to HTML and wrapped as a component
  file. It is not a port of the React source: the Tailwind classes carry
  the design, which is the part that took the work, and they are the same
  in every framework.

  This block is interactive in React and the handlers are NOT here.
  Buttons, toggles and menus render in their initial state and do
  nothing until you wire them up.
-->
<div class="mx-auto w-full max-w-2xl p-6 ">
  <style>.hl-meter { -webkit-appearance: none; appearance: none; background: none; }
  .hl-meter::-webkit-meter-bar {
    background: var(--muted); border: none; border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-optimum-value {
    background: var(--color-emerald-500, #10b981); border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-suboptimum-value {
    background: var(--color-amber-500, #f59e0b); border-radius: 9999px;
  }
  .hl-meter::-webkit-meter-even-less-good-value {
    background: var(--color-rose-500, #f43f5e); border-radius: 9999px;
  }
  .hl-meter::-moz-meter-bar { border-radius: 9999px; }</style>
  <div class="mb-4 flex flex-wrap items-center gap-3">
    <div class="min-w-0 flex-auto">
      <h3 data-stress-ignore="true" class="text-sm font-semibold">Retrieved context</h3>
      <p class="mt-0.5 text-xs text-muted-foreground">4 chunks for “why did northwind not get the volume price”, ranked by similarity</p>
    </div>
    <div role="radiogroup" aria-label="Filter by source type" class="flex gap-1">
      <button type="button" role="radio" aria-checked="true" tabindex="0" class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring border-primary bg-primary/10 text-primary">All</button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring border-border/60 text-muted-foreground hover:bg-muted">Documents</button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring border-border/60 text-muted-foreground hover:bg-muted">Tables</button>
      <button type="button" role="radio" aria-checked="false" tabindex="-1" class="rounded-full border px-2.5 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring border-border/60 text-muted-foreground hover:bg-muted">Web</button>
    </div>
  </div>
  <ol class="space-y-3">
    <li class="overflow-hidden rounded-2xl border border-border/60 bg-card">
      <div class="flex flex-wrap items-center gap-2.5 border-b border-border/60 px-4 py-2.5">
        <span aria-hidden="true" class="flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-muted font-mono text-[10px] font-bold text-muted-foreground">1</span>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-text h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true">
          <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path>
          <path d="M14 2v4a2 2 0 0 0 2 2h4"></path>
          <path d="M10 9H8"></path>
          <path d="M16 13H8"></path>
          <path d="M16 17H8"></path>
        </svg>
        <span class="min-w-0 truncate text-xs font-semibold"><span class="sr-only">Document: </span>Pricing handbook</span>
        <span class="min-w-0 shrink truncate text-xs text-muted-foreground">· §4.2 — Volume breaks</span>
        <span class="ms-auto flex shrink-0 items-center gap-2">
          <span class="text-[11px] text-muted-foreground">Strong match</span>
          <meter value="0.91" min="0" max="1" low="0.6" high="0.8" optimum="1" aria-label="Similarity 0.91" class="hl-meter h-1.5 w-12">0.91</meter>
          <span class="font-mono text-[11px] tabular-nums text-muted-foreground">0.91</span>
        </span>
      </div>
      <div class="px-4 py-3">
        <p id="chunk-1" class="text-xs leading-relaxed text-muted-foreground line-clamp-4">Accounts above 1,200 units per order qualify for the tier-two rate of $8.40, held for the duration of the contract year. <mark class="rounded bg-primary/15 px-0.5 text-foreground">The break is applied per order, not per quarter</mark>, so a single large order beats two smaller ones of the same total volume.</p>
        <button type="button" aria-expanded="false" aria-controls="chunk-1" class="mt-2 inline-flex items-center gap-1 rounded-md text-xs font-medium text-primary transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">Show full chunk<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3 w-3 transition-transform" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button>
      </div>
    </li>
    <li class="overflow-hidden rounded-2xl border border-border/60 bg-card">
      <div class="flex flex-wrap items-center gap-2.5 border-b border-border/60 px-4 py-2.5">
        <span aria-hidden="true" class="flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-muted font-mono text-[10px] font-bold text-muted-foreground">2</span>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true">
          <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
        </svg>
        <span class="min-w-0 truncate text-xs font-semibold"><span class="sr-only">Support ticket: </span>Ticket #4192</span>
        <span class="min-w-0 shrink truncate text-xs text-muted-foreground">· Northwind Retail · resolved</span>
        <span class="ms-auto flex shrink-0 items-center gap-2">
          <span class="text-[11px] text-muted-foreground">Partial match</span>
          <meter value="0.78" min="0" max="1" low="0.6" high="0.8" optimum="1" aria-label="Similarity 0.78" class="hl-meter h-1.5 w-12">0.78</meter>
          <span class="font-mono text-[11px] tabular-nums text-muted-foreground">0.78</span>
        </span>
      </div>
      <div class="px-4 py-3">
        <p id="chunk-2" class="text-xs leading-relaxed text-muted-foreground line-clamp-4">Customer asked why their November order did not get the tier-two price. Their October and November orders were 700 units each — <mark class="rounded bg-primary/15 px-0.5 text-foreground">combined they clear the break, individually they do not</mark>. Confirmed with finance that this is working as intended.</p>
        <button type="button" aria-expanded="false" aria-controls="chunk-2" class="mt-2 inline-flex items-center gap-1 rounded-md text-xs font-medium text-primary transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">Show full chunk<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3 w-3 transition-transform" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button>
      </div>
    </li>
    <li class="overflow-hidden rounded-2xl border border-border/60 bg-card">
      <div class="flex flex-wrap items-center gap-2.5 border-b border-border/60 px-4 py-2.5">
        <span aria-hidden="true" class="flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-muted font-mono text-[10px] font-bold text-muted-foreground">3</span>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-database h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true">
          <ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
          <path d="M3 5V19A9 3 0 0 0 21 19V5"></path>
          <path d="M3 12A9 3 0 0 0 21 12"></path>
        </svg>
        <span class="min-w-0 truncate text-xs font-semibold"><span class="sr-only">Table: </span>warehouse.orders</span>
        <span class="min-w-0 shrink truncate text-xs text-muted-foreground">· 2,481 rows scanned</span>
        <span class="ms-auto flex shrink-0 items-center gap-2">
          <span class="text-[11px] text-muted-foreground">Partial match</span>
          <meter value="0.64" min="0" max="1" low="0.6" high="0.8" optimum="1" aria-label="Similarity 0.64" class="hl-meter h-1.5 w-12">0.64</meter>
          <span class="font-mono text-[11px] tabular-nums text-muted-foreground">0.64</span>
        </span>
      </div>
      <div class="px-4 py-3">
        <p id="chunk-3" class="text-xs leading-relaxed text-muted-foreground line-clamp-4">Aggregated order volume by account for the trailing four quarters, filtered to accounts with more than one order in the period.</p>
        <button type="button" aria-expanded="false" aria-controls="chunk-3" class="mt-2 inline-flex items-center gap-1 rounded-md text-xs font-medium text-primary transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">Show full chunk<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3 w-3 transition-transform" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button>
      </div>
    </li>
    <li class="overflow-hidden rounded-2xl border border-border/60 bg-card">
      <div class="flex flex-wrap items-center gap-2.5 border-b border-border/60 px-4 py-2.5">
        <span aria-hidden="true" class="flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-muted font-mono text-[10px] font-bold text-muted-foreground">4</span>
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true">
          <circle cx="12" cy="12" r="10"></circle>
          <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"></path>
          <path d="M2 12h20"></path>
        </svg>
        <span class="min-w-0 truncate text-xs font-semibold"><span class="sr-only">Web page: </span>supplier.example.com</span>
        <span class="min-w-0 shrink truncate text-xs text-muted-foreground">· Lead times · fetched today</span>
        <span class="ms-auto flex shrink-0 items-center gap-2">
          <span class="text-[11px] text-muted-foreground">Weak match</span>
          <meter value="0.41" min="0" max="1" low="0.6" high="0.8" optimum="1" aria-label="Similarity 0.41" class="hl-meter h-1.5 w-12">0.41</meter>
          <span class="font-mono text-[11px] tabular-nums text-muted-foreground">0.41</span>
        </span>
      </div>
      <div class="px-4 py-3">
        <p id="chunk-4" class="text-xs leading-relaxed text-muted-foreground line-clamp-4">Standard lead time for the winter blend is <mark class="rounded bg-primary/15 px-0.5 text-foreground">21 days from purchase order</mark>. Expedited shipping is available at a 15% surcharge and reduces this to 9 days.</p>
        <button type="button" aria-expanded="false" aria-controls="chunk-4" class="mt-2 inline-flex items-center gap-1 rounded-md text-xs font-medium text-primary transition-opacity hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">Show full chunk<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down h-3 w-3 transition-transform" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></button>
      </div>
    </li>
  </ol>
</div>
  • This is rendered HTML, not a translation of the React source. The Tailwind classes carry the design and work in any framework.
  • It is one frame: the component in its initial state, with no props applied beyond the defaults.
  • This block is interactive in React — toggles, menus or form state. None of that survives here; the markup is the closed/default state and the handlers are gone. Re-wire them in your own framework.
  • Requires Tailwind, and the design tokens the classes reference (bg-card, text-muted-foreground, and so on). The template ZIPs ship a globals.css that defines them.

What each framework gets across the whole catalog — effects convert properly; this rung is markup.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

Used in these pages

Want the whole screen instead of this one section? Open a page and copy it entire.

8 more blocks in Retrieval & Context

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse Retrieval & Context

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once

More Retrieval & Context blocks

View category
Open the full page for this block

Answer Source Citations

The published footnotes under an answer — ordered list, real cite and time elements, the claim each source supports, and a staleness warning written in words rather than shown as an amber dot.

Retrieval & Context231 lines1 dep
Open the full page for this block

Knowledge Source Picker

Choosing what the assistant may read, with indexing state told honestly — a half-indexed source says so rather than showing a green tick — and the footer stating the effect of the selection, not its cardinality.

Retrieval & Context345 lines1 dep
Open the full page for this block

Nothing Found, Answered Honestly

The refusal screen: says plainly that nothing was found, shows where it looked and the near-misses below threshold, and offers the two real ways out instead of inventing an answer.

Retrieval & Context180 lines1 dep
Open the full page for this block

Context Window Budget

What is in the context and what fell out of it — a stacked budget with the reply reserve drawn as a segment, and the dropped chunks named with the reason, which is the part nobody ships.

Retrieval & Context309 lines1 dep
Open the full page for this block

Index Freshness Panel

A green tick means the connector works, not that the answers are current. Freshness as an age in words per source, the headline taken from the stalest one, and a failed sync that still says what age it is answering from.

Retrieval & Context246 lines1 dep
Open the full page for this block

Retrieval Freshness List

Retrieved sources ranked by how stale they are, because a confident answer from a two-year-old document is the failure people do not catch.

Retrieval & Context151 linesNo deps
Open the full page for this block

Context Scope List

Which sources this answer may draw on, shown as a scope the user can read before asking rather than a citation list after.

Retrieval & Context152 linesNo deps
Open the full page for this block

Grounding Split

How an answer is tied to its sources, and what the system does when it cannot find any.

Retrieval & Context156 linesNo deps