Sources and context
Where an answer came from, and what it cost. Sources is the collapsed list under a turn,
InlineCitation is the marker inside the prose, and Context is the token budget.
Import
import {
Source,
Sources,
SourcesContent,
SourcesTrigger,
} from '@elirobinson/ai-elements/components/sources';
import {
InlineCitation,
InlineCitationCard,
InlineCitationCardBody,
InlineCitationCardTrigger,
InlineCitationText,
} from '@elirobinson/ai-elements/components/inline-citation';
import {
Context,
ContextContent,
ContextTrigger,
} from '@elirobinson/ai-elements/components/context';The link, and what it says
Source renders an <a href target="_blank" rel="noreferrer">. Three things follow:
relisnoreferrer, and that is the whole of it: the HTML spec definesnoreferreras implyingnoopener, and atarget="_blank"link is implicitlynoopenerin current browsers regardless. There is nothing to add.{...props}is spread last, so the only reason to override it is wanting the referrer sent —<Source rel="noopener" …>.- The visible text is the
titleyou pass, not the host of thehref. Nothing derives the label from the URL. This is the one that matters, because these URLs came from a model which got them from retrieved content: a link can say one thing and go somewhere else unless you build the label out of the URL yourself. SourcesTriggerreads "Used N sources" from itscountprop. The count is a number you give it; it is not derived from the children.
Inline citations
Target Size (Minimum) is 24 by 24.example.com
This demo and the one below it are shown closed. Both previews are hover cards, which render
into a portal on document.body — open by default they would sit on top of the page rather
than inside the box — so opening one is the reader's move.
InlineCitationCardTrigger is not a number. It renders new URL(sources[0]).hostname,
plus +N when there is more than one — so the marker in the prose is the host, and it is
derived from the URL rather than supplied beside it.
Two consequences worth knowing before you wire it to model output:
new URL()throws on anything that is not an absolute URL. A relative or malformed href from a model takes the render down with it; validate before you pass it.- The trigger is a
Badge, which renders a<span>. It has notabindexand norole, so it is not in the tab order and the hover card is a pointer-only affordance. Pass your own focusable element as the trigger if the preview has to be reachable.
InlineCitationCarouselPrev and …Next are real buttons with aria-labels of "Previous"
and "Next", both floored to var(--target) — see below.
Context
ContextTrigger's default child is a Button, so unlike the citation marker this hover card
does open on focus as well as on hover. The percentage it shows is
usedTokens / maxTokens formatted with a hard-coded en-US locale; pass your own child to
format it yourself.
What this system changed
| Patch | What changed, and why |
|---|---|
button-floor | primary — ui/button.tsx — every <Button> in the tree. Measured default 36px, sm 32px, icon 36x36, icon-sm 32x32, icon-lg 40x40. The floor every vendored button gets unless it is classified dense elsewhere in this list. shadcn sizes at 32-40px, which clears AA and misses this system’s AAA default; the `not-data-[touch-target=dense]` guard is what keeps that a default rather than a blanket, and it cannot be undone by a consumer’s className the way a merged utility could. |
sources-trigger-floor | primary — sources.tsx — SourcesTrigger. Measured 110x16. A 16px-tall disclosure row misses the dense floor too, so there is no second tier to fall back to. |
source-link-floor | primary — sources.tsx — Source (the citation link). Measured 152x16. A citation marker: an a[href] that navigates away from the page, sitting in a list where every row is free to be taller. |
inline-citation-carousel-paging-floor | primary — inline-citation.tsx — InlineCitationCarouselPrev and …Next. Measured 16x16 each. Carousel paging is pagination, which contracts.json names as primary in the same breath as buttons and nav items. |
Read from vendoredElementTargets in @elirobinson/ai-patterns/contracts, which the browser audit writes.