Join our Discord Community

React AI Inline Citation

Academic-style inline citations with hover details. Build credible AI content with React, Next.js, and TypeScript, featuring source carousels and contextual information for shadcn/ui applications.

Trying to implement AI Elements?

Join our Discord community for help from other developers.


Academic-style citations for AI content that needs to show sources and build trust. Hover to see source details, navigate between multiple sources, and give users a way to verify claims in React applications.

Inline citation with sources

Citation badges with hover details and source carousel:

Loading component...

Shows source hostnames with counts, reveals detailed info on hover, and provides carousel navigation for multiple sources in TypeScript components. Works with any citation format—just pass URLs and metadata.

Installation

npx shadcn@latest add https://www.shadcn.io/registry/ai.json
npx shadcn@latest add https://www.shadcn.io/registry/ai.json
pnpm dlx shadcn@latest add https://www.shadcn.io/registry/ai.json
bunx shadcn@latest add https://www.shadcn.io/registry/ai.json

Usage

import {
  InlineCitation,
  InlineCitationCard,
  InlineCitationCardBody,
  InlineCitationCardTrigger,
  InlineCitationCarousel,
  InlineCitationCarouselContent,
  InlineCitationCarouselItem,
  InlineCitationSource,
  InlineCitationText,
} from "@/components/ai/inline-citation";

<InlineCitation>
  <InlineCitationText>This text has a citation</InlineCitationText>
  <InlineCitationCard>
    <InlineCitationCardTrigger sources={["https://example.com"]} />
    <InlineCitationCardBody>
      <InlineCitationCarousel>
        <InlineCitationCarouselContent>
          <InlineCitationCarouselItem>
            <InlineCitationSource
              title="Example Source"
              url="https://example.com"
              description="A reliable source"
            />
          </InlineCitationCarouselItem>
        </InlineCitationCarouselContent>
      </InlineCitationCarousel>
    </InlineCitationCardBody>
  </InlineCitationCard>
</InlineCitation>;

Most AI apps dump source links at the end like an afterthought in React applications. Users can't tell which claim comes from which source. Inline citations connect claims to sources immediately.

Academic papers figured this out decades ago. The [1] format lets readers verify specific claims without losing their place in the content in Next.js projects.

Usage with Vercel AI SDK

Generate cited content using Vercel AI SDK's experimental_useObject for structured citation data in React applications:

"use client";

import { experimental_useObject as useObject } from "@ai-sdk/react";
import {
  InlineCitation,
  InlineCitationCard,
  InlineCitationCardTrigger,
  InlineCitationCardBody,
  InlineCitationCarousel,
  InlineCitationCarouselContent,
  InlineCitationCarouselItem,
  InlineCitationCarouselHeader,
  InlineCitationCarouselIndex,
  InlineCitationSource,
  InlineCitationQuote,
} from "@/components/ai/inline-citation";
import { Button } from "@/components/ui/button";
import { z } from "zod";

const citationSchema = z.object({
  content: z.string(),
  citations: z.array(
    z.object({
      number: z.string(),
      title: z.string(),
      url: z.string(),
      description: z.string().optional(),
      quote: z.string().optional(),
    })
  ),
});

export default function CitationDemo() {
  const { object, submit, isLoading } = useObject({
    api: "/api/citation",
    schema: citationSchema,
  });

  return (
    <div className="space-y-4">
      <Button
        onClick={() => submit({ prompt: "artificial intelligence" })}
        disabled={isLoading}
      >
        Generate AI Content
      </Button>

      {object?.content && (
        <div className="prose max-w-none">
          <p>
            {object.content.split(/(\[\d+\])/).map((part, index) => {
              const citationMatch = part.match(/\[(\d+)\]/);
              if (citationMatch) {
                const citationNumber = citationMatch[1];
                const citation = object.citations?.find(
                  (c) => c.number === citationNumber
                );

                if (citation) {
                  return (
                    <InlineCitation key={index}>
                      <InlineCitationCard>
                        <InlineCitationCardTrigger sources={[citation.url]} />
                        <InlineCitationCardBody>
                          <InlineCitationCarousel>
                            <InlineCitationCarouselHeader>
                              <InlineCitationCarouselIndex />
                            </InlineCitationCarouselHeader>
                            <InlineCitationCarouselContent>
                              <InlineCitationCarouselItem>
                                <InlineCitationSource
                                  title={citation.title}
                                  url={citation.url}
                                  description={citation.description}
                                />
                                {citation.quote && (
                                  <InlineCitationQuote>
                                    {citation.quote}
                                  </InlineCitationQuote>
                                )}
                              </InlineCitationCarouselItem>
                            </InlineCitationCarouselContent>
                          </InlineCitationCarousel>
                        </InlineCitationCardBody>
                      </InlineCitationCard>
                    </InlineCitation>
                  );
                }
              }
              return part;
            })}
          </p>
        </div>
      )}
    </div>
  );
}

Backend schema for citation generation:

// app/api/citation/route.ts
import { streamObject } from "ai";
import { z } from "zod";

const citationSchema = z.object({
  content: z.string(),
  citations: z.array(
    z.object({
      number: z.string(),
      title: z.string(),
      url: z.string(),
      description: z.string().optional(),
      quote: z.string().optional(),
    })
  ),
});

export async function POST(req: Request) {
  const { prompt } = await req.json();

  const result = streamObject({
    model: "openai/gpt-4o",
    schema: citationSchema,
    prompt: `Generate content about ${prompt} with proper citations marked as [1], [2], etc.`,
  });

  return result.toTextStreamResponse();
}

Features

  • Hover cards that don't break on mobile (click also works) in React applications
  • Carousel navigation for multiple sources per citation in Next.js projects
  • Shows hostname and source count in badges with TypeScript support
  • Optional quote blocks for relevant excerpts
  • Keyboard navigation between sources in JavaScript implementations
  • Works with any Vercel AI SDK setup in modern React frameworks
  • Free open source component designed for research AI, fact-checking, and credible content generation

API Reference

InlineCitation

Container for citation text and card.

PropTypeDescription
...propsComponentProps<'span'>Spreads to root span element

InlineCitationText

Styled text that shows hover effects.

PropTypeDescription
...propsComponentProps<'span'>Spreads to span element

InlineCitationCard

Hover card container for citation details.

PropTypeDescription
...propsComponentProps<typeof HoverCard>Spreads to HoverCard component

InlineCitationCardTrigger

Badge trigger showing source hostname and count.

PropTypeDescription
sourcesstring[]Required - Array of source URLs
...propsComponentProps<typeof Badge>Spreads to Badge component

InlineCitationCardBody

Content container for citation details.

PropTypeDescription
...propsComponentProps<'div'>Spreads to div element

InlineCitationCarousel

Carousel for navigating multiple citations.

PropTypeDescription
...propsComponentProps<typeof Carousel>Spreads to Carousel component

InlineCitationCarouselContent

Content wrapper for carousel items.

PropTypeDescription
...propsComponentProps<'div'>Spreads to CarouselContent

InlineCitationCarouselItem

Individual citation item in carousel.

PropTypeDescription
...propsComponentProps<'div'>Spreads to CarouselItem

InlineCitationSource

Source information display.

PropTypeDescription
titlestringSource title
urlstringSource URL
descriptionstringSource description
...propsComponentProps<'div'>Spreads to div element

InlineCitationQuote

Styled blockquote for excerpts.

PropTypeDescription
...propsComponentProps<'blockquote'>Spreads to blockquote element

Keyboard interactions

KeyDescription
TabFocus citation trigger
Enter / SpaceOpen citation card
EscapeClose citation card
ArrowLeft / ArrowRightNavigate carousel

Citation gotchas that will bite you

Invalid URLs break hostname extraction: The badge shows the hostname from the URL in React applications. Bad URLs will show 'unknown' or crash the component.

Too many sources overwhelm users: Keep it to 3-5 sources per citation max in Next.js projects. More than that and nobody reads them.

Mobile hover doesn't work: Hover cards fail on touch devices in JavaScript implementations. This component supports click, but test on mobile thoroughly.

AI hallucinated citations: Validate that cited sources actually contain the claimed information in TypeScript applications. LLMs make up citations constantly.

Badge sizing breaks with long hostnames: Very long domain names can break the layout in React components. Consider truncating hostnames.

Integration with other components

Works great in Response components for markdown content in React applications. Drop into Message for cited chat responses in Next.js projects. Combine with Actions for citation management like copy or verification. This free open source component integrates seamlessly with modern JavaScript frameworks.

Questions developers actually ask