# Inline Citation

A source link inside the sentence it supports, with a preview of the page on hover.

Source: https://suiss.app/ui/docs/ai/inline-citation

**React**

**Example**

```tsx
import { buttonVariants } from "@/components/ui/button"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"

type Source = {
  url: string
  title: string
  description: string
}

const sources = {
  streaming: {
    url: "https://nextjs.org/docs/app/getting-started/linking-and-navigating#streaming",
    title: "Linking and Navigating",
    description: "How the App Router streams a route to the browser.",
  },
  suspense: {
    url: "https://react.dev/reference/react/Suspense",
    title: "<Suspense>",
    description: "Display a fallback until its children have finished loading.",
  },
  serverComponents: {
    url: "https://react.dev/reference/rsc/server-components",
    title: "Server Components",
    description: "Components that render ahead of time, on the server.",
  },
  loading: {
    url: "https://nextjs.org/docs/app/api-reference/file-conventions/loading",
    title: "loading.js",
    description: "Show an instant loading state for a route segment.",
  },
  chunked: {
    url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding",
    title: "Transfer-Encoding",
    description: "Send a response as a series of chunks.",
  },
} satisfies Record<string, Source>

const WWW = /^www\./

function hostname(url: string) {
  return new URL(url).hostname.replace(WWW, "")
}

function Citation({ source }: { source: Source }) {
  const host = hostname(source.url)

  return (
    <HoverCard>
      <HoverCardTrigger
        href={source.url}
        target="_blank"
        rel="noreferrer"
        className={buttonVariants({ variant: "secondary", size: "xs" })}
      >
        {host}
        <span className="sr-only"> (opens in a new tab)</span>
      </HoverCardTrigger>
      <HoverCardContent className="flex w-64 flex-col gap-1 text-sm">
        <div className="font-medium">{source.title}</div>
        <p className="text-muted-foreground">{source.description}</p>
        <span className="text-muted-foreground text-xs">{host}</span>
      </HoverCardContent>
    </HoverCard>
  )
}

export default function InlineCitationDemo() {
  return (
    <div className="mx-auto flex max-w-sm flex-col gap-3 py-6 text-sm leading-relaxed">
      <p>
        Next.js streams a route in chunks, so the page shows up before every
        request finishes. <Citation source={sources.streaming} />
      </p>
      <p>
        Wrap a slow part of the page in a Suspense boundary and React sends the
        fallback first. When the data resolves, the finished HTML streams into
        the same response and replaces the fallback, with no extra request from
        the browser. <Citation source={sources.suspense} /> Server Components
        help here, because their code never ships to the browser.{" "}
        <Citation source={sources.serverComponents} />
      </p>
      <p>
        A loading.tsx file does the same for a whole route segment.{" "}
        <Citation source={sources.loading} /> It all runs over chunked transfer
        encoding, so there is nothing extra to set up.{" "}
        <Citation source={sources.chunked} />
      </p>
    </div>
  )
}
```

Inline Citation is a source that sits in the sentence it supports. The trigger is a link to the source, styled as a small [Button](https://suiss.app/ui/docs/components/button), and a [Hover Card](https://suiss.app/ui/docs/components/hover-card) previews its title and description.

To show a list of sources after the reply, use [Sources](https://suiss.app/ui/docs/ai/sources).

## Installation

Inline Citation is built with the [Hover Card](https://suiss.app/ui/docs/components/hover-card) and [Button](https://suiss.app/ui/docs/components/button) components.

```bash title="Terminal"
npx shadcn@latest add @esuiss/hover-card @esuiss/button
```

Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add hover-card button
```

## Usage

```tsx
<HoverCard>
  <HoverCardTrigger
    href={url}
    target="_blank"
    rel="noreferrer"
    className={buttonVariants({ variant: "secondary", size: "xs" })}
  >
    {hostname}
    <span className="sr-only"> (opens in a new tab)</span>
  </HoverCardTrigger>
  <HoverCardContent className="flex w-64 flex-col gap-1 text-sm">
    <div className="font-medium">{title}</div>
    <p className="text-muted-foreground">{description}</p>
  </HoverCardContent>
</HoverCard>
```

## AI SDK

A `source-url` part is the citation. Set `sendSources: true` on `toUIMessageStreamResponse()`, or write the part yourself.

```ts title="app/api/chat/route.ts"
writer.write({
  type: "source-url",
  sourceId: "suiss",
  url: "https://suiss.app/ui",
  title: "suiss/ui",
})
```

```tsx
const sources = message.parts.filter((part) => part.type === "source-url")
```

`title` is optional. Fall back to the URL's hostname.

## API Reference

See [Hover Card](https://suiss.app/ui/docs/components/hover-card#api-reference) and [Button](https://suiss.app/ui/docs/components/button#api-reference).

## Accessibility

- The trigger is a real link, so the source opens with a click or Enter, and the preview opens on focus as well as on hover.
- Add "(opens in a new tab)" to the link text with `sr-only`.
- The hover card repeats what the link names, so readers who cannot hover lose nothing.

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from "@/components/ui/popover"
import { Text } from "@/components/ui/text"

export function AiInlineCitationExample() {
  return (
    <View className="w-full gap-2">
      <Text>
        Suspense shows a fallback until its children finish loading, so the page
        can stream the rest of the content first.
      </Text>
      <View className="items-start">
        <Popover>
          <PopoverTrigger render={<Button variant="secondary" size="xs" />}>
            <Text>react.dev</Text>
          </PopoverTrigger>
          <PopoverContent align="start" className="w-72">
            <PopoverHeader>
              <PopoverTitle>&lt;Suspense&gt; – React</PopoverTitle>
              <PopoverDescription>
                Lets you display a fallback until its children have finished
                loading.
              </PopoverDescription>
            </PopoverHeader>
          </PopoverContent>
        </Popover>
      </View>
    </View>
  )
}
```

```swift
import EsuissUI
import SwiftUI

struct InlineCitationExample: View {
  @State private var open = false

  var body: some View {
    VStack(alignment: .leading, spacing: 8) {
      Text("Suspense shows a fallback until its children finish loading, so the page can stream the rest of the content first.")
        .textStyle(.base)
        .foregroundStyle(EsuissColor.foreground)
      Button("react.dev") { open = true }
        .buttonStyle(.variant(.secondary, size: .xs))
        .popover(isPresented: $open) {
          PopoverContent(width: 288) {
            PopoverHeader {
              PopoverTitle(verbatim: "<Suspense> – React")
              PopoverDescription("Lets you display a fallback until its children have finished loading.")
            }
          }
        }
    }
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.Popover
import com.esuiss.ui.components.PopoverDescription
import com.esuiss.ui.components.PopoverHeader
import com.esuiss.ui.components.PopoverTitle
import com.esuiss.ui.components.PopupAlign
import com.esuiss.ui.components.Text

@Composable
fun InlineCitationAIDefault() {
    var open by rememberSaveable { mutableStateOf(false) }
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
        Text("Suspense shows a fallback until its children finish loading, so the page can stream the rest of the content first.")
        Popover(
            open = open,
            onOpenChange = { open = it },
            trigger = { Button("react.dev", onClick = { open = true }, variant = ButtonVariant.Secondary, size = ButtonSize.Xs) },
            modifier = Modifier.width(288.dp),
            align = PopupAlign.Start,
        ) {
            PopoverHeader {
                PopoverTitle("<Suspense> – React")
                PopoverDescription("Lets you display a fallback until its children have finished loading.")
            }
        }
    }
}
```

Inline Citation on mobile is built from the same parts as on the web: [Popover](https://suiss.app/ui/docs/components/popover), [Button](https://suiss.app/ui/docs/components/button). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add popover button
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add popover button --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add popover button --platform kotlin
```

## API Reference

See [Popover](https://suiss.app/ui/docs/components/popover), [Button](https://suiss.app/ui/docs/components/button).

