<!-- GENERATED by scripts/build-llms.mjs from llms/retrieval.md — do not edit this file. -->

# `lr-citation-badge`

- **Import** `import '@aceshooting/lyra-ui/components/lr-citation-badge.js';` (stable tag alias; registers the tag)
- **Class** `LyraCitationBadge`, also available unregistered from `@aceshooting/lyra-ui/components/retrieval/citation-badge/citation-badge.class.js`
- **Family** `components/retrieval/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 4 parts, 8 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-citation-badge`

An inline `[n]` citation marker with a hover/focus preview popover and confidence/verification-status
coloring. First-party invention (no Web Awesome equivalent). Meant to sit inline in a chat message's
text, each badge carrying a `source-id` that matches a corresponding `<lr-source-card>` shown
elsewhere on the page (a sibling component in this family) — this component never imports or knows
anything about `<lr-source-card>`, it only carries the id through its event details.

**Properties:**

- `index: number = 1` — the citation number shown, e.g. `3` renders as `[3]`.
- `status: CitationBadgeStatus = 'default'` (reflected) — one of `'default' | 'high' | 'medium' |
'low' | 'verified' | 'unverified'`; drives the badge's color and (unless `label` is set) part of
  its accessible name.
- `sourceId: string = ''` (attribute `source-id`) — id of a corresponding `<lr-source-card>`,
  echoed back verbatim in both events; never read or validated by this component.
- `href: string = ''` — optional direct link target for the citation's source, carried into
  `lr-citation-open`'s detail as-is; this component never navigates.
- `label: string = ''` — adds caller-supplied context to the localized citation-button name while
  retaining the visible citation number (for example, `"Citation 3, Annual report"`). Authored host
  `aria-label` independently names the component and is not cloned onto that nested button. Host
  naming does not cross the shadow boundary, so the button retains its own localized
  citation/index/status name

**Events:**

- `lr-citation-activate` (`detail: { sourceId: string; index: number }`) — fires on click, or on
  Enter while focused (native `<button>` behavior, no listener needed for the Enter case). The
  lightweight "jump to this source" signal.
- `lr-citation-open` (`detail: { sourceId: string; index: number; href?: string }`) — fires on
  dblclick, or on Space while focused. A distinct "full preview" signal; `href` is `undefined` when
  the `href` prop isn't set. A double-click also fires two `lr-citation-activate` events (one per
  constituent click, standard browser `dblclick` behavior) in addition to the one `lr-citation-open`.

**Slots:** default — rich preview/tooltip content (e.g. a filename + excerpt), shown in a floating
popover on hover/focus. This is _not_ the badge's visible content (the badge always renders
`[index]`); nothing renders at all (no hover affordance) when this slot is empty.
When populated, the button carries `aria-describedby` referencing the same-shadow-tree popover,
which owns `role="tooltip"` whether currently shown or hidden.

**CSS parts:** `base` (the clickable `<button>`), `bracket` (each of the two literal `[`/`]` glyphs),
`index` (the citation number), `popover` (the floating preview panel, only meaningful while open).

**Themeable custom properties:** `--lr-citation-badge-accent` / `--lr-citation-badge-bg` /
`--lr-citation-badge-border`. Their private defaults follow `status`, but an inherited or direct
public value remains authoritative in every status. Shared tokens include
`--lr-color-text-quiet`, `--lr-color-text`, `--lr-color-success` / `-success-quiet`,
`--lr-color-warning` / `-warning-quiet`, `--lr-color-danger` / `-danger-quiet`, `--lr-radius`,
`--lr-color-surface`, `--lr-color-border`, `--lr-shadow`, `--lr-space-s`/`-m`,
`--lr-transition-fast`, `--lr-focus-ring-*`.

> Retheming a group of badges from outside `<lr-citation-badge>` (e.g. per-source or
> per-confidence colors)? Set the component hooks above on their ancestor wrapper. Use
> `--lr-theme-*` instead only when changing a shared semantic palette input for the entire subtree.

The anchored source-preview popover is a floating surface and paints from the **shared overlay-surface family** (16.0.0):
`--lr-overlay-surface` (default `var(--lr-color-surface-overlay)`), `--lr-overlay-border` (default
`var(--lr-color-border)`) and `--lr-overlay-shadow-anchored` (default `var(--lr-shadow-m)`). None is
declared on `:host`, so one declaration on `:root` — or on any ancestor, to scope it — retints this
surface together with every other floating surface in the library. `--lr-overlay-radius` (default `var(--lr-radius)`) is the matching corner radius.

`--lr-positioning-strategy` (16.0.0) — the source-preview popover reads this same cascading
`absolute`/`fixed` override documented on `<lr-popover>` when it is (re)positioned, falling back to
its own `fixed` default when nothing is set. There is no per-instance `positioning-strategy`
property on `<lr-citation-badge>`; set the custom property on `:root`, a theme, or one clipping
ancestor to change every unset citation badge beneath it.

**Optional peer deps:** none.

```html
<p>
  Revenue grew 12% year over year
  <lr-citation-badge index="1" status="verified" source-id="doc-1">
    <strong>annual_report.pdf</strong> — "Revenue grew 12% year over year,
    driven primarily by..." </lr-citation-badge
  >.
</p>
<script type="module">
  document.addEventListener("lr-citation-activate", (e) => {
    document
      .querySelector(`lr-source-card[source-id="${e.detail.sourceId}"]`)
      ?.scrollIntoView({ block: "center" });
  });
</script>
```

The popover is positioned with the same internal `place()` helper (`top-start` placement) that
`<lr-tool-call-chip>` uses for its own detail tooltip, and never traps focus — it's supplementary
preview content, not a modal, so Tab continues past the badge normally even while the popover happens
to be visible from a mouse hover. Hovering and focus are tracked as independent "keep it open"
reasons (mirroring `<lr-toast-item>`'s hovering/focused pair), so the pointer leaving while the
badge still holds keyboard focus doesn't schedule a hide the focus is still holding open. There's a
200ms grace period before a hover/focus-out actually hides the popover, so moving the pointer from
the badge into the popover itself (to select/copy its text) doesn't make it vanish mid-move; Escape
and blur (Tab away) close it immediately instead, with no delay.

Status coloring follows a semantic scheme: `verified`/`high` use the success tones (a claim that's
been checked, or the model is confident in); `medium`/`low` use warning tones; `unverified` uses the
_danger_ tone — deliberately distinct from `low`, since "hasn't been checked at all" is a different
(arguably riskier) claim than "checked but uncertain". `default` renders as plain neutral text with
no background tint, for citations that carry no confidence/verification signal at all.

**Known gotchas:**

- Enter and Space are given distinct meanings (Enter = activate via native `<button>` click, Space =
  open) — Space's native click-on-keyup is pre-empted with `preventDefault()` on keydown so it fires
  `lr-citation-open` instead of triggering a second `lr-citation-activate`.
- Escape closes the popover but calls `stopPropagation()`, so it won't also close a surrounding
  `<lr-dialog>` that has its own Escape-to-close handler.
- The preview slot's presence is tracked in JS (`hasPreviewSlot`), not via CSS `:empty` — the
  `[part="popover"]` always contains a literal `<slot>` child, so `:empty` would never match even
  with nothing assigned.

---
