Citation Inline Card
ns-ui/citation-inline-cardFreeComponent
An inline citation pill (hostname + source count, Geist Mono) that opens one bordered card with a title, excerpt and a 1/N stepper, instead of a popover per source.
Live preview
live · rendered by the registry
Rendered by ns-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://design.helpmarq.com/r/citation-inline-card.jsonSource
1"use client";23import {4 useEffect,5 useId,6 useRef,7 useState,8 type KeyboardEvent as ReactKeyboardEvent,9} from "react";10import { createPortal } from "react-dom";1112// MarginCite — an inline citation primitive. The trigger is one monochrome13// superscript pill (hostname of the primary source, plus a small source14// count when there's more than one) sitting right in the text flow. Opening15// it reveals ONE bordered card — title, excerpt, a "1 / N" stepper — instead16// of a popover-per-source; three sources behind a claim is one card the17// reader pages through, not three stacked callouts.18//19// The card is rendered through a portal into document.body and positioned20// with `position: fixed`, coordinates read from the trigger's own21// getBoundingClientRect on open, on scroll and on resize. That's the fix for22// the same clipping hazard tooltips and menus hit: a card left as a plain23// `absolute` child of the trigger gets silently cut off by the first24// ancestor with overflow-hidden (a common wrapper around body copy). It also25// flips above the trigger when there isn't room below, and clamps26// horizontally so it never runs off the viewport edge.27//28// Fully keyboard-operable: the trigger is a real button (Enter/Space toggle29// it natively), focus moves into the open card so Tab reaches Previous,30// Next and the source link in order, Left/Right arrow keys step the reader31// through sources without leaving the keyboard, and Escape closes the card32// and returns focus to the trigger. A visually-hidden status region33// announces "Source 2 of 3" on every step so paging is legible to assistive34// tech, not just sighted users watching the "1 / N" readout update.35//36// Zero dependencies beyond react-dom's createPortal. No canvas — the card is37// plain DOM, and every color is a token (--background --foreground --muted38// --border --accent) so both themes render correctly. Under39// prefers-reduced-motion the entrance animation is dropped via a CSS media40// query; the card still opens instantly and is fully operable.4142export type CiteSource = {43 id: string;44 title: string;45 excerpt: string;46 /** full URL; hostname is derived from it for both the pill and the card footer */47 url: string;48};4950export interface MarginCiteProps {51 sources: CiteSource[];52 className?: string;53}5455const GAP = 8; // px between trigger and card56const CARD_WIDTH = 320;57const VIEWPORT_MARGIN = 12; // px clamp from viewport edges5859function hostnameOf(url: string): string {60// … truncated
Files it writes
More from ns-ui
All 228 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordion Latchaccordion-latch | ns-ui | Components | Free | no deps | |
| Approval Inline Diffapproval-inline-diff | ns-ui | Components | Free | no deps | |
| ASCII Engraving Contourascii-engraving-contour | ns-ui | Components | Free | no deps | |
| ASCII Globe Spinascii-globe-spin | ns-ui | Components | Free | no deps | |
| ASCII Torus Donutascii-torus-donut | ns-ui | Components | Free | no deps | |
| Autosave Ratchetautosave-ratchet | ns-ui | Components | Free | no deps | |
| Avatar Stack Flockavatar-stack-flock | ns-ui | Components | Free | no deps | |
| Background ASCII Ditherbackground-ascii-dither | ns-ui | Components | Free | no deps |
