Linked article
This page has not been reviewed by a human.
You write articles with hand-written links to Sefaria sources. Readers often want to glance at the passage without leaving your page. A plain link sends them away, and they may not come back.
This example solves that. A click on a citation opens a small dialog with the passage. The example fetches text only when a preview opens. It leaves your article text untouched.
This is a complete app (Vite and TypeScript) in examples/linked-article. It is shown running below. You can't edit it on this page. To read its source, see the example on GitHub.
How it works
The link you write
An authored link is an ordinary link to sefaria.org. It also carries the reference in a data-sefaria-ref attribute, such as Micah 6:8.
<a
href="https://www.sefaria.org/Micah.6.8"
data-sefaria-ref="Micah 6:8"
>Micah 6:8</a
>The app enhances only a[data-sefaria-ref] links that have a non-blank reference and an href that is an https address on www.sefaria.org.
The app checks whether a link qualifies here:
function isEligibleAnchor(anchor: HTMLAnchorElement): boolean {
const tref = anchor.dataset.sefariaRef;
if (tref === undefined || tref.trim().length === 0) {
return false;
}
try {
const url = new URL(anchor.href);
return (
url.protocol === "https:" &&
url.hostname === "www.sefaria.org" &&
url.username.length === 0 &&
url.password.length === 0
);
} catch {
return false;
}
}
function shouldEnhanceActivation(
event: MouseEvent,
anchor: HTMLAnchorElement,
): boolean {
return (
!event.defaultPrevented &&
event.button === 0 &&
!event.altKey &&
!event.ctrlKey &&
!event.metaKey &&
!event.shiftKey &&
!anchor.hasAttribute("download") &&
(anchor.target === "" || anchor.target === "_self")
);
}The preview
The app intercepts an ordinary click. An ordinary click means the primary button, with no Alt, Ctrl, Shift or Cmd key, no download attribute and no other target. The app then opens a native <dialog> that holds a <sefaria-source-card>.
// Each opening owns its nodes so queued close/error events cannot close a newer preview.
const dialog = root.createElement("dialog");
dialog.id = DIALOG_ID;
dialog.className = "source-preview";
dialog.setAttribute("aria-label", "Sefaria source preview");
const closeButton = root.createElement("button");
closeButton.type = "button";
closeButton.textContent = "Close source preview";
closeButton.autofocus = true;
const card = root.createElement("sefaria-source-card") as SefariaSourceCard;
card.source = source;
card.setAttribute("sref", tref);
const events = new AbortController();
const preview = {
card,
anchor,
close(): void {
if (active !== preview) return;
active = undefined;
events.abort();
card.setAttribute("sref", "");
dialog.close();
dialog.remove();
if (preview.anchor.isConnected) preview.anchor.focus();
},
};
active = preview;
closeButton.addEventListener("click", preview.close, {
signal: events.signal,
});
dialog.addEventListener(
"cancel",
(event) => {
event.preventDefault();
preview.close();
},
{ signal: events.signal },
);
dialog.addEventListener("close", preview.close, { signal: events.signal });
card.addEventListener(
"sefaria-source-card-error",
(event: Event) => {
const { error, sref } = (
event as CustomEvent<{
readonly error: unknown;
readonly sref: string;
}>
).detail;
if (active !== preview || card.sref !== sref) return;
preview.close();
status.textContent =
error instanceof Error ? error.message : String(error);
status.setAttribute("role", "alert");
},
{ signal: events.signal },
);
dialog.append(closeButton, card);
root.body.append(dialog);
dialog.showModal();
closeButton.focus();The card gets its sref only at that moment, so nothing loads before a click. The app creates its own client with the cache off and passes it to the card as its data source (the source property). The card makes one request, or two when a requested translation language is missing.
What readers get
- Ctrl-, Cmd-, Shift- or middle-click, the context menu,
target="_blank"and pages without JavaScript keep the normal link to Sefaria. - The dialog is modal, so you close it before using another article link. If another activation reaches the app while the dialog is open, it reuses the dialog and changes the reference.
- Escape or the Close button closes the dialog. Focus returns to the link if it is still connected to the page.
- If the request fails or the response is invalid, the dialog closes and a status area on the page shows the error (
role="alert"). If Sefaria returns a documented HTTP error, such as a 404, the dialog stays open and the card shows the alert. To retry after that error, close the dialog and open it again. - The app adds only
aria-controlsto eligible links.destroy()removes its listeners and restores them.
What it doesn't do
It doesn't find citations in your text. You write the links.
For automatic detection, use Sefaria's Linker. It scans a page and links the citations it finds.
This example stores the authored reference in a data attribute. It differs from the Linker in two ways:
- You write the links by hand. The Linker detects them.
- The preview opens in a modal Source Card dialog. The Linker uses its own popups.
It doesn't submit your article or Linker tracking data. Opening a preview still sends a text request to Sefaria.
Run it locally
From the repository root, use Node.js 22.12 or later and the pnpm version pinned in package.json. Build the libraries first, because the example imports their built files:
pnpm install
pnpm build
pnpm dev:linked-articleNext steps
- Show an attributed passage covers the Source Card used here.
- Sefaria's own texts and tools explains how this toolkit relates to Sefaria's Linker.