Inline Citation
A hoverable citation component that displays source information and quotes inline with text, perfect for AI-generated content with references.
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.
According to recent studies, artificial intelligence has shown remarkable progress in natural language processing. The technology continues to evolve rapidly, with new breakthroughs being announced regularlyexample.com +5.
"use client";import { InlineCitation, InlineCitationCard, InlineCitationCardBody, InlineCitationCardTrigger, InlineCitationCarousel, InlineCitationCarouselContent, InlineCitationCarouselHeader, InlineCitationCarouselIndex, InlineCitationCarouselItem, InlineCitationCarouselNext, InlineCitationCarouselPrev, InlineCitationSource, InlineCitationText,} from "@/components/ai-elements/elements/inline-citation";const citation = { text: "The technology continues to evolve rapidly, with new breakthroughs being announced regularly", sources: [ { title: "Advances in Natural Language Processing", url: "https://example.com/nlp-advances", description: "A comprehensive study on the recent developments in natural language processing technologies and their applications.", }, { title: "Breakthroughs in Machine Learning", url: "https://mlnews.org/breakthroughs", description: "An overview of the most significant machine learning breakthroughs in the past year.", }, { title: "AI in Healthcare: Current Trends", url: "https://healthai.com/trends", description: "A report on how artificial intelligence is transforming healthcare and diagnostics.", }, { title: "Ethics of Artificial Intelligence", url: "https://aiethics.org/overview", description: "A discussion on the ethical considerations and challenges in the development of AI.", }, { title: "Scaling Deep Learning Models", url: "https://deeplearninghub.com/scaling-models", description: "Insights into the technical challenges and solutions for scaling deep learning architectures.", }, { title: "Natural Language Understanding Benchmarks", url: "https://nlubenchmarks.com/latest", description: "A summary of the latest benchmarks and evaluation metrics for natural language understanding systems.", }, ],};const Example = () => ( <p className="text-sm leading-relaxed"> According to recent studies, artificial intelligence has shown remarkable progress in natural language processing.{" "} <InlineCitation> <InlineCitationText>{citation.text}</InlineCitationText> <InlineCitationCard> <InlineCitationCardTrigger sources={citation.sources.map((source) => source.url)} /> <InlineCitationCardBody> <InlineCitationCarousel> <InlineCitationCarouselHeader> <InlineCitationCarouselPrev /> <InlineCitationCarouselNext /> <InlineCitationCarouselIndex /> </InlineCitationCarouselHeader> <InlineCitationCarouselContent> {citation.sources.map((source) => ( <InlineCitationCarouselItem key={source.url}> <InlineCitationSource description={source.description} title={source.title} url={source.url} /> </InlineCitationCarouselItem> ))} </InlineCitationCarouselContent> </InlineCitationCarousel> </InlineCitationCardBody> </InlineCitationCard> </InlineCitation> . </p>);export default Example;Installation
npx ai-elements@latest add inline-citationnpx shadcn@latest add @ai-elements/inline-citation"use client";import { Badge } from "@repo/shadcn-ui/components/ui/badge";import { Carousel, type CarouselApi, CarouselContent, CarouselItem,} from "@repo/shadcn-ui/components/ui/carousel";import { HoverCard, HoverCardContent, HoverCardTrigger,} from "@repo/shadcn-ui/components/ui/hover-card";import { cn } from "@repo/shadcn-ui/lib/utils";import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";import { type ComponentProps, createContext, useCallback, useContext, useEffect, useState,} from "react";export type InlineCitationProps = ComponentProps<"span">;export const InlineCitation = ({ className, ...props}: InlineCitationProps) => ( <span className={cn("group inline items-center gap-1", className)} {...props} />);export type InlineCitationTextProps = ComponentProps<"span">;export const InlineCitationText = ({ className, ...props}: InlineCitationTextProps) => ( <span className={cn("transition-colors group-hover:bg-accent", className)} {...props} />);export type InlineCitationCardProps = ComponentProps<typeof HoverCard>;export const InlineCitationCard = (props: InlineCitationCardProps) => ( <HoverCard closeDelay={0} openDelay={0} {...props} />);export type InlineCitationCardTriggerProps = ComponentProps<typeof Badge> & { sources: string[];};export const InlineCitationCardTrigger = ({ sources, className, ...props}: InlineCitationCardTriggerProps) => ( <HoverCardTrigger asChild> <Badge className={cn("ml-1 rounded-full", className)} variant="secondary" {...props} > {sources.length ? ( <> {new URL(sources[0]).hostname}{" "} {sources.length > 1 && `+${sources.length - 1}`} </> ) : ( "unknown" )} </Badge> </HoverCardTrigger>);export type InlineCitationCardBodyProps = ComponentProps<"div">;export const InlineCitationCardBody = ({ className, ...props}: InlineCitationCardBodyProps) => ( <HoverCardContent className={cn("relative w-80 p-0", className)} {...props} />);const CarouselApiContext = createContext<CarouselApi | undefined>(undefined);const useCarouselApi = () => { const context = useContext(CarouselApiContext); return context;};export type InlineCitationCarouselProps = ComponentProps<typeof Carousel>;export const InlineCitationCarousel = ({ className, children, ...props}: InlineCitationCarouselProps) => { const [api, setApi] = useState<CarouselApi>(); return ( <CarouselApiContext.Provider value={api}> <Carousel className={cn("w-full", className)} setApi={setApi} {...props}> {children} </Carousel> </CarouselApiContext.Provider> );};export type InlineCitationCarouselContentProps = ComponentProps<"div">;export const InlineCitationCarouselContent = ( props: InlineCitationCarouselContentProps) => <CarouselContent {...props} />;export type InlineCitationCarouselItemProps = ComponentProps<"div">;export const InlineCitationCarouselItem = ({ className, ...props}: InlineCitationCarouselItemProps) => ( <CarouselItem className={cn("w-full space-y-2 p-4 pl-8", className)} {...props} />);export type InlineCitationCarouselHeaderProps = ComponentProps<"div">;export const InlineCitationCarouselHeader = ({ className, ...props}: InlineCitationCarouselHeaderProps) => ( <div className={cn( "flex items-center justify-between gap-2 rounded-t-md bg-secondary p-2", className )} {...props} />);export type InlineCitationCarouselIndexProps = ComponentProps<"div">;export const InlineCitationCarouselIndex = ({ children, className, ...props}: InlineCitationCarouselIndexProps) => { const api = useCarouselApi(); const [current, setCurrent] = useState(0); const [count, setCount] = useState(0); useEffect(() => { if (!api) { return; } setCount(api.scrollSnapList().length); setCurrent(api.selectedScrollSnap() + 1); api.on("select", () => { setCurrent(api.selectedScrollSnap() + 1); }); }, [api]); return ( <div className={cn( "flex flex-1 items-center justify-end px-3 py-1 text-muted-foreground text-xs", className )} {...props} > {children ?? `${current}/${count}`} </div> );};export type InlineCitationCarouselPrevProps = ComponentProps<"button">;export const InlineCitationCarouselPrev = ({ className, ...props}: InlineCitationCarouselPrevProps) => { const api = useCarouselApi(); const handleClick = useCallback(() => { if (api) { api.scrollPrev(); } }, [api]); return ( <button aria-label="Previous" className={cn("shrink-0", className)} onClick={handleClick} type="button" {...props} > <ArrowLeftIcon className="size-4 text-muted-foreground" /> </button> );};export type InlineCitationCarouselNextProps = ComponentProps<"button">;export const InlineCitationCarouselNext = ({ className, ...props}: InlineCitationCarouselNextProps) => { const api = useCarouselApi(); const handleClick = useCallback(() => { if (api) { api.scrollNext(); } }, [api]); return ( <button aria-label="Next" className={cn("shrink-0", className)} onClick={handleClick} type="button" {...props} > <ArrowRightIcon className="size-4 text-muted-foreground" /> </button> );};export type InlineCitationSourceProps = ComponentProps<"div"> & { title?: string; url?: string; description?: string;};export const InlineCitationSource = ({ title, url, description, className, children, ...props}: InlineCitationSourceProps) => ( <div className={cn("space-y-1", className)} {...props}> {title && ( <h4 className="truncate font-medium text-sm leading-tight">{title}</h4> )} {url && ( <p className="truncate break-all text-muted-foreground text-xs">{url}</p> )} {description && ( <p className="line-clamp-3 text-muted-foreground text-sm leading-relaxed"> {description} </p> )} {children} </div>);export type InlineCitationQuoteProps = ComponentProps<"blockquote">;export const InlineCitationQuote = ({ children, className, ...props}: InlineCitationQuoteProps) => ( <blockquote className={cn( "border-muted border-l-2 pl-3 text-muted-foreground text-sm italic", className )} {...props} > {children} </blockquote>);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 />
Prop
Type
<InlineCitationText />
Prop
Type
<InlineCitationCard />
Prop
Type
<InlineCitationCardTrigger />
Prop
Type
<InlineCitationCardBody />
Prop
Type
<InlineCitationCarousel />
Prop
Type
<InlineCitationCarouselContent />
Prop
Type
<InlineCitationCarouselItem />
Prop
Type
<InlineCitationCarouselHeader />
Prop
Type
<InlineCitationCarouselIndex />
Prop
Type
<InlineCitationCarouselPrev />
Prop
Type
<InlineCitationCarouselNext />
Prop
Type
<InlineCitationSource />
Prop
Type
<InlineCitationQuote />
Prop
Type