# Inline Citations

Superscript citation markers with a compact source footer.

- Category: Text Outputs
- Source: AICSS (https://www.aicss.dev/components/inline-citations)
- Author: @kvnkld (https://x.com/kvnkld)
- Styling: Self-contained CSS with CSS custom properties (design tokens). Theme-aware via [data-theme] (light/dark).

## Instructions

Add this component to the project. Keep the styling self-contained. Map the design tokens (CSS custom properties) to the project's theme if they are not already defined.

## Code

### React - `InlineCitations.tsx`

```tsx
import styles from "./InlineCitations.module.css";

type CiteRef = { n: number; label: string; host: string; url: string };

const TEXT =
  "Transformers scale well with data and compute[1], though attention is quadratic in sequence length[2].";
const REFS: CiteRef[] = [
  { n: 1, label: "Attention Is All You Need", host: "arxiv.org", url: "https://arxiv.org/abs/1706.03762" },
  { n: 2, label: "Efficient Transformers: A Survey", host: "arxiv.org", url: "https://arxiv.org/abs/2009.06732" },
];

function CiteArrow() {
  return (
    <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
      <path d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" />
    </svg>
  );
}

export function InlineCitations({
  text = TEXT,
  refs = REFS,
}: {
  text?: string;
  refs?: CiteRef[];
}) {
  // Markers like [1] in the text become small numbered chips that link to
  // the source and reveal the reference name in a tooltip on hover.
  const parts = text.split(/(\[\d+\])/g);
  return (
    <div className={styles.citeProse}>
      <p>
        {parts.map((part, i) => {
          const m = part.match(/^\[(\d+)\]$/);
          if (!m) return <span key={i}>{part}</span>;
          const r = refs.find((x) => x.n === Number(m[1]));
          return r ? (
            <span key={i} className={styles.citeTip}>
              <a className={styles.citeMark} href={r.url} target="_blank" rel="noreferrer">{r.n}</a>
              <span className={styles.citeTipBox} role="tooltip">{r.label}</span>
            </span>
          ) : (
            <span key={i} className={styles.citeMark}>{m[1]}</span>
          );
        })}
      </p>
      <div className={styles.citeFooter}>
        {refs.map((r) => (
          <a key={r.n} className={styles.citeRef} href={r.url} target="_blank" rel="noreferrer">
            <span className={styles.citeMark}>{r.n}</span>
            <span className={styles.citeRefLabel}>{r.label}</span>
            <span className={styles.citeSep}>·</span>
            <span className={styles.citeRefHost}>{r.host}</span>
            <span className={styles.citeArrow} aria-hidden><CiteArrow /></span>
          </a>
        ))}
      </div>
    </div>
  );
}
```

### React - `InlineCitations.module.css`

```css
.citeProse { font-size: 14px; line-height: 19px; color: #1a1a1a; }
.citeProse p { margin: 0; }
.citeMark { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; flex: none; border-radius: 4px; background: #f4f5f7; color: #a1a1a1; font-size: 9px; font-weight: 600; line-height: 1; vertical-align: 5.5px; margin: 0 2px; }
a.citeMark { cursor: pointer; text-decoration: none; transition: color 0.15s, background 0.15s; }
a.citeMark:hover { color: #1a1a1a; background: #e6e8ec; }
.citeTip { position: relative; display: inline; }
.citeTipBox { position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%) translateY(1px); font-size: 10px; line-height: 1; font-weight: 500; color: rgba(255, 255, 255, 0.9); background: rgba(29, 29, 29, 0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); padding: 4px 5px; border-radius: 6px; white-space: nowrap; pointer-events: none; opacity: 0; filter: blur(2px); transition: opacity 0.15s ease, transform 0.15s ease, filter 0.15s ease; z-index: 1000; }
.citeTip:hover .citeTipBox, .citeTip:focus-within .citeTipBox { opacity: 1; filter: blur(0); transform: translateX(-50%) translateY(0); }
.citeFooter { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #e6e8ec; }
.citeRef { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: #a1a1a1; min-width: 0; text-decoration: none; cursor: pointer; }
.citeRef .citeMark { margin: 0; }
.citeRefLabel { color: #1a1a1a; font-weight: 450; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.citeSep { color: #a1a1a1; flex: none; }
.citeRefHost { color: #a1a1a1; flex: none; white-space: nowrap; transition: color 0.16s; }
.citeArrow { display: inline-flex; flex: none; margin-left: -2px; color: #a1a1a1; opacity: 0; transform: rotate(45deg) translate(0, 2px); transition: opacity 0.16s, transform 0.22s; pointer-events: none; }
.citeRef:hover .citeArrow { opacity: 1; transform: rotate(45deg) translate(0, 0); }
.citeRef:hover .citeRefHost { color: #1a1a1a; }
@media (prefers-color-scheme: dark) {
  .citeProse { color: #f5f5f5; }
  .citeMark { background: #424242; }
  a.citeMark:hover { color: #f5f5f5; background: #525252; }
  .citeFooter { border-top-color: #303030; }
  .citeSep { color: #737373; }
  .citeArrow { color: #737373; }
  .citeRefLabel { color: #f5f5f5; }
  .citeRef:hover .citeRefHost { color: #f5f5f5; }
}
```

### Vue - `InlineCitations.vue`

```vue
<template>
  <div class="cite-prose">
    <p>
      <template v-for="(p, i) in segments" :key="i">
        <span v-if="p.mark" class="cite-tip"><a class="cite-mark" :href="p.url" target="_blank" rel="noreferrer">{{ p.value }}</a><span class="cite-tip-box" role="tooltip">{{ p.label }}</span></span><span v-else>{{ p.value }}</span>
      </template>
    </p>
    <div class="cite-footer">
      <a v-for="r in refs" :key="r.n" class="cite-ref" :href="r.url" target="_blank" rel="noreferrer">
        <span class="cite-mark">{{ r.n }}</span>
        <span class="cite-ref-label">{{ r.label }}</span>
        <span class="cite-sep">·</span>
        <span class="cite-ref-host">{{ r.host }}</span>
        <span class="cite-arrow" aria-hidden="true">
          <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" /></svg>
        </span>
      </a>
    </div>
  </div>
</template>

<script setup>
import { computed } from "vue";
const props = defineProps({
  text: {
    type: String,
    default: "Transformers scale well with data and compute[1], though attention is quadratic in sequence length[2].",
  },
  refs: {
    type: Array,
    default: () => [
      { n: 1, label: "Attention Is All You Need", host: "arxiv.org", url: "https://arxiv.org/abs/1706.03762" },
      { n: 2, label: "Efficient Transformers: A Survey", host: "arxiv.org", url: "https://arxiv.org/abs/2009.06732" },
    ],
  },
});
const segments = computed(() =>
  props.text.split(/(\[\d+\])/g).map((value) => {
    const m = value.match(/^\[(\d+)\]$/);
    if (!m) return { mark: false, value };
    const r = props.refs.find((x) => x.n === Number(m[1]));
    return { mark: true, value: m[1], url: r?.url, label: r?.label };
  })
);
</script>

<style scoped>
.cite-prose { font-size: 14px; line-height: 19px; color: #1a1a1a; }
.cite-prose p { margin: 0; }
.cite-mark { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; flex: none; border-radius: 4px; background: #f4f5f7; color: #a1a1a1; font-size: 9px; font-weight: 600; line-height: 1; vertical-align: 5.5px; margin: 0 2px; }
a.cite-mark { cursor: pointer; text-decoration: none; transition: color 0.15s, background 0.15s; }
a.cite-mark:hover { color: #1a1a1a; background: #e6e8ec; }
.cite-tip { position: relative; display: inline; }
.cite-tip-box { position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%) translateY(1px); font-size: 10px; line-height: 1; font-weight: 500; color: rgba(255, 255, 255, 0.9); background: rgba(29, 29, 29, 0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); padding: 4px 5px; border-radius: 6px; white-space: nowrap; pointer-events: none; opacity: 0; filter: blur(2px); transition: opacity 0.15s ease, transform 0.15s ease, filter 0.15s ease; z-index: 1000; }
.cite-tip:hover .cite-tip-box, .cite-tip:focus-within .cite-tip-box { opacity: 1; filter: blur(0); transform: translateX(-50%) translateY(0); }
.cite-footer { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #e6e8ec; }
.cite-ref { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: #a1a1a1; min-width: 0; text-decoration: none; cursor: pointer; }
.cite-ref .cite-mark { margin: 0; }
.cite-ref-label { color: #1a1a1a; font-weight: 450; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cite-sep { color: #a1a1a1; flex: none; }
.cite-ref-host { color: #a1a1a1; flex: none; white-space: nowrap; transition: color 0.16s; }
.cite-arrow { display: inline-flex; flex: none; margin-left: -2px; color: #a1a1a1; opacity: 0; transform: rotate(45deg) translate(0, 2px); transition: opacity 0.16s, transform 0.22s; pointer-events: none; }
.cite-ref:hover .cite-arrow { opacity: 1; transform: rotate(45deg) translate(0, 0); }
.cite-ref:hover .cite-ref-host { color: #1a1a1a; }
@media (prefers-color-scheme: dark) {
  .cite-prose { color: #f5f5f5; }
  .cite-mark { background: #424242; }
  a.cite-mark:hover { color: #f5f5f5; background: #525252; }
  .cite-footer { border-top-color: #303030; }
  .cite-sep { color: #737373; }
  .cite-arrow { color: #737373; }
  .cite-ref-label { color: #f5f5f5; }
  .cite-ref:hover .cite-ref-host { color: #f5f5f5; }
}
</style>
```

### Svelte - `InlineCitations.svelte`

```svelte
<script>
  export let text =
    "Transformers scale well with data and compute[1], though attention is quadratic in sequence length[2].";
  export let refs = [
    { n: 1, label: "Attention Is All You Need", host: "arxiv.org", url: "https://arxiv.org/abs/1706.03762" },
    { n: 2, label: "Efficient Transformers: A Survey", host: "arxiv.org", url: "https://arxiv.org/abs/2009.06732" },
  ];
  $: segments = text.split(/(\[\d+\])/g).map((value) => {
    const m = value.match(/^\[(\d+)\]$/);
    if (!m) return { mark: false, value };
    const r = refs.find((x) => x.n === Number(m[1]));
    return { mark: true, value: m[1], url: r?.url, label: r?.label };
  });
</script>

<div class="cite-prose">
  <p>{#each segments as p}{#if p.mark}<span class="cite-tip"><a class="cite-mark" href={p.url} target="_blank" rel="noreferrer">{p.value}</a><span class="cite-tip-box" role="tooltip">{p.label}</span></span>{:else}{p.value}{/if}{/each}</p>
  <div class="cite-footer">
    {#each refs as r (r.n)}
      <a class="cite-ref" href={r.url} target="_blank" rel="noreferrer">
        <span class="cite-mark">{r.n}</span>
        <span class="cite-ref-label">{r.label}</span>
        <span class="cite-sep">·</span>
        <span class="cite-ref-host">{r.host}</span>
        <span class="cite-arrow" aria-hidden="true">
          <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" /></svg>
        </span>
      </a>
    {/each}
  </div>
</div>

<style>
  .cite-prose { font-size: 14px; line-height: 19px; color: #1a1a1a; }
  .cite-prose p { margin: 0; }
  .cite-mark { display: inline-flex; align-items: center; justify-content: center; width: 12px; height: 12px; flex: none; border-radius: 4px; background: #f4f5f7; color: #a1a1a1; font-size: 9px; font-weight: 600; line-height: 1; vertical-align: 5.5px; margin: 0 2px; }
  a.cite-mark { cursor: pointer; text-decoration: none; transition: color 0.15s, background 0.15s; }
  a.cite-mark:hover { color: #1a1a1a; background: #e6e8ec; }
  .cite-tip { position: relative; display: inline; }
  .cite-tip-box { position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%) translateY(1px); font-size: 10px; line-height: 1; font-weight: 500; color: rgba(255, 255, 255, 0.9); background: rgba(29, 29, 29, 0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); padding: 4px 5px; border-radius: 6px; white-space: nowrap; pointer-events: none; opacity: 0; filter: blur(2px); transition: opacity 0.15s ease, transform 0.15s ease, filter 0.15s ease; z-index: 1000; }
  .cite-tip:hover .cite-tip-box, .cite-tip:focus-within .cite-tip-box { opacity: 1; filter: blur(0); transform: translateX(-50%) translateY(0); }
  .cite-footer { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #e6e8ec; }
  .cite-ref { display: flex; align-items: center; gap: 6px; font-size: 12px; line-height: 18px; color: #a1a1a1; min-width: 0; text-decoration: none; cursor: pointer; }
  .cite-ref .cite-mark { margin: 0; }
  .cite-ref-label { color: #1a1a1a; font-weight: 450; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .cite-sep { color: #a1a1a1; flex: none; }
  .cite-ref-host { color: #a1a1a1; flex: none; white-space: nowrap; transition: color 0.16s; }
  .cite-arrow { display: inline-flex; flex: none; margin-left: -2px; color: #a1a1a1; opacity: 0; transform: rotate(45deg) translate(0, 2px); transition: opacity 0.16s, transform 0.22s; pointer-events: none; }
  .cite-ref:hover .cite-arrow { opacity: 1; transform: rotate(45deg) translate(0, 0); }
  .cite-ref:hover .cite-ref-host { color: #1a1a1a; }
  @media (prefers-color-scheme: dark) {
    .cite-prose { color: #f5f5f5; }
    .cite-mark { background: #424242; }
    a.cite-mark:hover { color: #f5f5f5; background: #525252; }
    .cite-footer { border-top-color: #303030; }
    .cite-sep { color: #737373; }
    .cite-arrow { color: #737373; }
    .cite-ref-label { color: #f5f5f5; }
    .cite-ref:hover .cite-ref-host { color: #f5f5f5; }
  }
</style>
```
