Citation

The mark an answer leaves where a claim came from: a chip in the running text that previews its sources on hover or focus, and goes to them on click. One chip per claim, however many sources back it.

Largest Contentful Paint marks the moment the main content of a page becomes visible1. A good score is 2.5 seconds or less, measured at the 75th percentile of real page loads2. It is one of the three Core Web Vitals, alongside INP and CLS3.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add citation

Usage#

usage.tsxTSX
import { Citation } from "@/components/ui/citation"const source = {  href: "https://web.dev/articles/lcp",  title: "Largest Contentful Paint (LCP)",  description: "Sites should strive to have LCP of 2.5 seconds or less.",}export function Answer() {  return (    <p>      A good LCP is 2.5 seconds or less      <Citation index={1} sources={[source]} />.    </p>  )}

Anatomy#

Most of the time <Citation sources={…} /> is all you write: it renders the chip and the card from the data. Compose the parts when the chip needs its own label or the card its own content.

import {  Citation,  CitationTrigger,  CitationContent,  CitationSource,} from "@/components/ui/citation"<Citation sources={sources}>  <CitationTrigger />  <CitationContent>    <CitationSource />  </CitationContent></Citation>

The card#

Each source is a row: the favicon and domain as a quiet eyebrow, the page title, then the passage the claim rests on, clamped to three lines. Several sources stack as several rows in one card, each a link of its own, with its hover fill set 8px into the card on the Popover's inset rung so the corners stay concentric.

A good LCP is 2.5 seconds or less1.

Source chips#

variant="source" trades the counter for the favicon and the domain, with +N when more than one source backs the claim: the answer-engine register, where saying where without a hover is worth the width in the line. The counter stays the default because it reads quietest in dense prose.

Interaction to Next Paint reports the slowest interaction a visitor had on the page, not the first oneweb.dev. Field data for it comes from the Chrome UX Reportdeveloper.chrome.com+2.

In a chat#

Drop the chips straight into a Chat turn. The chip carries a data-slot, so the turn's prose styles leave it alone: markdown links get the underline, the chip keeps its own look.

What counts as a good LCP?

2.5 seconds or less, measured at the 75th percentile of page loads1. Between 2.5 and 4 seconds needs improvement, and anything slower is poor2.

Lab tools give you a single run; the number that counts is the field data from the Chrome UX Report3.

API reference#

Citation

The root: the sources and the HoverCard state every part reads. With no children it renders the chip and the card from sources. Forwards Radix HoverCard.Root props.

PropTypeDefaultDescription
sources*CitationSourceData[]-{ href, title?, description?, favicon? } per source. The chip links to the first.
indexnumber-The footnote number the number chip shows. Omitted, it shows the source count.
variant"number" | "source""number"number is a footnote counter; source shows the favicon, the domain and +N.
openDelaynumber300Hover intent before the card opens, in ms. Shorter than a plain HoverCard's 700.
closeDelaynumber200Delay before the card closes once the pointer leaves, in ms.
childrenReact.ReactNode-Compose the chip and card yourself with CitationTrigger and CitationContent.

CitationTrigger

The chip: a Badge on a real link to the first source, opened in a new tab. Its accessible name lists every source, because the card is never keyboard-reachable. Forwards native <a> props except href.

PropTypeDefaultDescription
childrenReact.ReactNode-Overrides the label (the index, or the domain and count).

CitationContent

The preview card. With no children it lists every source; pass CitationSources to compose it. Forwards HoverCardContent props (side, align, density).

CitationSource

One source in the card: favicon and domain, the title, the extract. A link. Forwards native <a> props.

PropTypeDefaultDescription
href*string-The page. The domain and favicon derive from it.
titlestring-The page title, clamped to two lines.
descriptionstring-The passage the claim rests on, clamped to three lines.
faviconstring-Overrides the favicon derived from the domain.

FAQ#

Because the chip marks a claim, not a link. Three chips in a row at the end of a sentence are noise the reader has to parse; one chip that opens onto three sources says the same thing and keeps the sentence readable.