Files
gh-nathanonn-claude-skills-…/docs/components/inline-citation.md
2025-11-30 08:41:51 +08:00

12 KiB

Inline Citation

URL: /components/inline-citation


title: Inline Citation description: A hoverable citation component that displays source information and quotes inline with text, perfect for AI-generated content with references. path: elements/components/inline-citation


The InlineCitation component provides a way to display citations inline with text content, similar to academic papers or research documents. It consists of a citation pill that shows detailed source information on hover, making it perfect for AI-generated content that needs to reference sources.

Installation

Usage

import {
    InlineCitation,
    InlineCitationCard,
    InlineCitationCardBody,
    InlineCitationCardTrigger,
    InlineCitationCarousel,
    InlineCitationCarouselContent,
    InlineCitationCarouselItem,
    InlineCitationCarouselHeader,
    InlineCitationCarouselIndex,
    InlineCitationSource,
    InlineCitationText,
} from "@/components/ai-elements/inline-citation";
<InlineCitation>
    <InlineCitationText>{citation.text}</InlineCitationText>
    <InlineCitationCard>
        <InlineCitationCardTrigger sources={citation.sources.map((source) => source.url)} />
        <InlineCitationCardBody>
            <InlineCitationCarousel>
                <InlineCitationCarouselHeader>
                    <InlineCitationCarouselIndex />
                </InlineCitationCarouselHeader>
                <InlineCitationCarouselContent>
                    <InlineCitationCarouselItem>
                        <InlineCitationSource title="AI SDK" url="https://ai-sdk.dev" description="The AI Toolkit for TypeScript" />
                    </InlineCitationCarouselItem>
                </InlineCitationCarouselContent>
            </InlineCitationCarousel>
        </InlineCitationCardBody>
    </InlineCitationCard>
</InlineCitation>

Usage with AI SDK

Build citations for AI-generated content using experimental_generateObject.

Add the following component to your frontend:

"use client";

import { experimental_useObject as useObject } from "@ai-sdk/react";
import {
    InlineCitation,
    InlineCitationText,
    InlineCitationCard,
    InlineCitationCardTrigger,
    InlineCitationCardBody,
    InlineCitationCarousel,
    InlineCitationCarouselContent,
    InlineCitationCarouselItem,
    InlineCitationCarouselHeader,
    InlineCitationCarouselIndex,
    InlineCitationCarouselPrev,
    InlineCitationCarouselNext,
    InlineCitationSource,
    InlineCitationQuote,
} from "@/components/ai-elements/inline-citation";
import { Button } from "@/components/ui/button";
import { citationSchema } from "@/app/api/citation/route";

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

    const handleSubmit = (topic: string) => {
        submit({ prompt: topic });
    };

    return (
        <div className="max-w-4xl mx-auto p-6 space-y-6">
            <div className="flex gap-2 mb-6">
                <Button onClick={() => handleSubmit("artificial intelligence")} disabled={isLoading} variant="outline">
                    Generate AI Content
                </Button>
                <Button onClick={() => handleSubmit("climate change")} disabled={isLoading} variant="outline">
                    Generate Climate Content
                </Button>
            </div>

            {isLoading && !object && <div className="text-muted-foreground">Generating content with citations...</div>}

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

                                if (citation) {
                                    return (
                                        <InlineCitation key={index}>
                                            <InlineCitationCard>
                                                <InlineCitationCardTrigger sources={[citation.url]} />
                                                <InlineCitationCardBody>
                                                    <InlineCitationCarousel>
                                                        <InlineCitationCarouselHeader>
                                                            <InlineCitationCarouselPrev />
                                                            <InlineCitationCarouselNext />
                                                            <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>
    );
};

export default CitationDemo;

Add the following route to your backend:

import { streamObject } from "ai";
import { z } from "zod";

export 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(),
        })
    ),
});

// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

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

    const result = streamObject({
        model: "openai/gpt-4o",
        schema: citationSchema,
        prompt: `Generate a well-researched paragraph about ${prompt} with proper citations. 
    
    Include:
    - A comprehensive paragraph with inline citations marked as [1], [2], etc.
    - 2-3 citations with realistic source information
    - Each citation should have a title, URL, and optional description/quote
    - Make the content informative and the sources credible
    
    Format citations as numbered references within the text.`,
    });

    return result.toTextStreamResponse();
}

Features

  • Hover interaction to reveal detailed citation information
  • Carousel navigation for multiple citations with prev/next controls
  • Live index tracking showing current slide position (e.g., "1/5")
  • Support for source titles, URLs, and descriptions
  • Optional quote blocks for relevant excerpts
  • Composable architecture for flexible citation formats
  • Accessible design with proper keyboard navigation
  • Seamless integration with AI-generated content
  • Clean visual design that doesn't disrupt reading flow
  • Smart badge display showing source hostname and count

Props

<InlineCitation />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the root span element.', type: 'React.ComponentProps<"span">', }, }} />

<InlineCitationText />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying span element.', type: 'React.ComponentProps<"span">', }, }} />

<InlineCitationCard />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the HoverCard component.', type: 'React.ComponentProps<"span">', }, }} />

<InlineCitationCardTrigger />

<TypeTable type={{ sources: { description: 'Array of source URLs. The length determines the number displayed in the badge.', type: 'string[]', }, '...props': { description: 'Any other props are spread to the underlying button element.', type: 'React.ComponentProps<"button">', }, }} />

<InlineCitationCardBody />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying div.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationCarousel />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying Carousel component.', type: 'React.ComponentProps', }, }} />

<InlineCitationCarouselContent />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying CarouselContent component.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationCarouselItem />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying div.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationCarouselHeader />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying div.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationCarouselIndex />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying div. Children will override the default index display.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationCarouselPrev />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying CarouselPrevious component.', type: 'React.ComponentProps', }, }} />

<InlineCitationCarouselNext />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying CarouselNext component.', type: 'React.ComponentProps', }, }} />

<InlineCitationSource />

<TypeTable type={{ title: { description: 'The title of the source.', type: 'string', }, url: { description: 'The URL of the source.', type: 'string', }, description: { description: 'A brief description of the source.', type: 'string', }, '...props': { description: 'Any other props are spread to the underlying div.', type: 'React.ComponentProps<"div">', }, }} />

<InlineCitationQuote />

<TypeTable type={{ '...props': { description: 'Any other props are spread to the underlying blockquote element.', type: 'React.ComponentProps<"blockquote">', }, }} />