Pro

Numbered citation markers for AI answers, each with a preview card on hover, focus or tap. Markers and the source list highlight each other.

Answer with citations

How do I stream an LLM’s answer in a Next.js app?

Return a ReadableStream from a Route Handler and Next.js sends each chunk to the browser as soon as it’s ready. On the client, read the response body with a reader and append each chunk to the message until the stream reports it’s done.

If the whole answer still lands at once, a proxy is buffering it: send X-Accel-Buffering: no from the route. If it stops partway, the function hit its time limit; raise maxDuration on that route.

As a row of chips

List opened

Installation

This is a Pro component. UI Polish Pro costs $149 one time and includes every Pro component, block and template, now and later. Add your license key to your MCP connection or components.json, then install it either way below.

With UI Polish connected, ask your agent:

Add the UI Polish sources to this project.

Or with the shadcn CLI:

npx shadcn@latest add @uipolish/sources
Package@base-ui/react
Brings inBrand marks, Icons, Text, Scroll area

Citations for an AI answer. Small numbered markers sit in the text; pointing at one, focusing it or tapping it opens a card with the source's site, title and a short excerpt. Below the answer, a source list uses the same numbers, either as a "4 sources" line that opens to a list or as a row of chips that scrolls sideways. Pointing at a list item highlights its markers in the text, and pointing at a marker highlights it everywhere it appears.

Three parts: <Sources> holds the list and the shared hover state, <Citation n={1} /> is a marker, <SourceList /> is the list.

The card is built on Base UI's <Popover> with openOnHover: it handles the hover delays, the click that pins it, dismissal, focus and positioning.

When to use it

  • Answers built from documents or web pages, where the reader should be able to check a claim against where it came from.
  • Text where the same source supports several sentences: the shared number and the highlight show which ones.

When not to

  • A single link the reader should follow: write a normal link in the sentence.
  • Footnotes in long documents a reader prints or reads start to end: a numbered notes section at the end serves them better than hover cards.
  • Sources you can't link to. A marker that opens a card with nowhere to go promises more than it gives; name the document in the text instead.

States

PartStateWhat you seeHow
MarkerRestNumber in a small pill, muted, a 6% fill
MarkerPointed at (here or from the list)Fill steps up, number goes to the text colordata-active
MarkerPressSame step, in 80ms:active
MarkerFocus2px ring; the card opens at once:focus-visible
CardOpening on hoverAfter 300ms over the marker; straight away if another card closed in the last 300mspointer type mouse only
CardOpenSite with favicon or letter, number, title, two-line excerptportalled, above the page
CardPinnedOpened by click or tap; stays until a click outside, Escape or Tab away
CardClosing150ms after the pointer leaves both marker and card
ListFoldedOverlapping favicons and "4 sources" with a chevronvariant="disclosure"
ListOpenNumbered rows: favicon, title, domain; an arrow shows on hover or focusaria-expanded
List rowPointed at6% fill; its markers in the text light updata-active
ChipsRestOne pill per source: number, favicon, title (one line, a long one fades at its end, full title in title); edges fade only where there's more to scrollvariant="row"

Marks. For a well-known site, pass brand (a name from brand-marks, such as "wikipedia", "reddit" or "stackoverflow"): its real mark shows in its own color on a round tile, the same marks Research progress uses. Otherwise pass favicon with an image URL if you have one. Without either, or if the image fails to load, the domain's first letter shows in a circle. Nothing is fetched from a favicon service.

Excerpts. One sentence from the page that supports the claim: "Leftovers should go in the fridge within two hours of cooking", not the page's meta description. The card shows two lines at most.

The card's edge is the one in --ui-shadow-floating: the same outline color as fields (--ui-border-strong), drawn once. No inset ring as well; the two together drew a 2px edge.

Motion

WhatDurationEasing
Card in: fade and scale from 0.97, from the marker's side--ui-dur-fast (160ms)--ui-ease-enter
Card out--ui-dur-instant (120ms)--ui-ease-exit
Hover open delay / close grace300ms / 150msn/a
Marker and row highlight--ui-dur-instant (120ms)--ui-ease-color
List opens--ui-dur-panel (280ms) height, --ui-dur-fast fade 75ms later--ui-ease-expand, --ui-ease-enter
List closes--ui-dur-base (200ms) height, --ui-dur-instant fade--ui-ease-collapse, --ui-ease-exit
Chevron turns a quarter turn, right to down--ui-dur-base (200ms)--ui-ease-glide
Row arrow fades in, nudges 1px up and out (Icon external)fade --ui-dur-instant (120ms), nudge --ui-dur-snap (240ms)--ui-ease-color, --ui-ease-snap

The card animates between Base UI's [data-starting-style] and [data-ending-style], and stays mounted until its fade ends. Its transform-origin is Base UI's --transform-origin, so it grows out of the marker and flips below it near the top of the screen.

Under prefers-reduced-motion: reduce the card fades without scaling and the list opens instantly.

Accessibility

  • Each marker is a <button> named "Source 1: Leftovers and food safety: how long cooked food can sit out", with aria-expanded and aria-controls pointing at its card. The visible number alone would be read as "1".
  • Keyboard focus opens the card at once. The card is portalled, but focus guards make Tab move from the marker to the card's link, and Tab again move on to whatever follows the marker and close it. Escape closes the card and returns focus to the marker without reopening it.
  • On touch, a tap opens the card and a tap anywhere else closes it. Markers get a 24px hit area (40px on coarse pointers) without changing how they sit in the line.
  • Every source link opens in a new tab with rel="noopener noreferrer" and says so to screen readers: "(opens in new tab)". Rows also show an arrow.
  • The list and the chip row are <ol> elements labelled "Sources", so the numbers are announced as positions too.
  • In the chip row, Tab reaches each chip; Left and Right arrows, Home and End move between chips; focused chips scroll clear of the edge fades.

Before / after

Before: bracketed numbers that do nothing, and a list of bare URLs.

TSX
<p>Leftovers should be refrigerated within two hours [1].</p><ul>  <li><a href="https://health.example.org/food-safety/leftovers">https://health.example.org/food-safety/leftovers</a></li></ul>

After: a quiet marker with a preview, and a list that matches it.

TSX
<Sources sources={sources}>  <p>    Leftovers should go in the fridge within two hours of cooking    <Citation n={1} />.  </p>  <SourceList /></Sources>

With the sources as a row of chips under a short answer:

TSX
<Sources sources={sources}>  <p>A fridge should stay at 40°F or below<Citation n={2} />.</p>  <SourceList variant="row" /></Sources>