# Sources

The links the agent used for a reply, in a row that opens into a list of links.

Source: https://suiss.app/ui/docs/ai/sources

**React**

**Example**

```tsx
import { ArrowUpRightIcon, GlobeIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const sources = [
  {
    url: "https://docs.astro.build/en/install-and-setup/",
    title: "Install Astro | docs.astro.build",
  },
  {
    url: "https://www.npmjs.com/package/create-astro",
    title: "create-astro | npmjs.com",
  },
  {
    url: "https://pnpm.io/cli/create",
    title: "pnpm create | pnpm.io",
  },
]

export default function SourcesDemo() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-4 py-6 text-sm">
      <Message align="end">
        <MessageContent>
          <Bubble align="end" variant="muted">
            <BubbleContent>
              How do I start an Astro project with pnpm?
            </BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <Message>
        <MessageContent className="gap-4">
          <p>
            Run pnpm create astro@latest and follow the prompts. It scaffolds
            the project and installs the dependencies.
          </p>
          <Trace>
            <TraceCollapsible defaultOpen>
              <TraceCollapsibleTrigger>
                <TraceLine>
                  <TraceIcon>
                    <GlobeIcon />
                  </TraceIcon>
                  <TraceTitle>Used {sources.length} sources</TraceTitle>
                </TraceLine>
              </TraceCollapsibleTrigger>
              <TraceCollapsibleContent>
                <Trace>
                  {sources.map((source) => (
                    <a
                      key={source.url}
                      href={source.url}
                      target="_blank"
                      rel="noreferrer"
                    >
                      <TraceLine>
                        <TraceTitle>
                          {source.title}
                          <span className="sr-only"> (opens in a new tab)</span>
                        </TraceTitle>
                        <TraceIcon>
                          <ArrowUpRightIcon />
                        </TraceIcon>
                      </TraceLine>
                    </a>
                  ))}
                </Trace>
              </TraceCollapsibleContent>
            </TraceCollapsible>
          </Trace>
        </MessageContent>
      </Message>
    </div>
  )
}
```

Sources is a row that lists the links the agent used for a reply. It says how many there are, such as "Used 3 sources", and opens into a link for each one.

The list is a `TraceCollapsible` with a nested `Trace` of links. To cite a source inside a sentence, use [Inline Citation](https://suiss.app/ui/docs/ai/inline-citation).

## Installation

Sources is built with the [Trace](https://suiss.app/ui/docs/components/trace) component.

```bash title="Terminal"
npx shadcn@latest add @esuiss/trace
```

Works in any project with a `components.json`. See [Installation](https://suiss.app/ui/docs/installation#add-components) for the one-line registry setup. Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

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

## Usage

```tsx
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"
```

```tsx
<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon>
          <GlobeIcon />
        </TraceIcon>
        <TraceTitle>Used {sources.length} sources</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <Trace>
        {sources.map((source) => (
          <a key={source.url} href={source.url} target="_blank" rel="noreferrer">
            <TraceLine>
              <TraceTitle>
                {source.title ?? new URL(source.url).hostname}
                <span className="sr-only"> (opens in a new tab)</span>
              </TraceTitle>
              <TraceIcon>
                <ArrowUpRightIcon />
              </TraceIcon>
            </TraceLine>
          </a>
        ))}
      </Trace>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>
```

## AI SDK

Collect the `source-url` parts of a message and render them after the text. The agent can cite the same page more than once, so dedupe them by `url`.

```tsx
const sources = message.parts
  .filter((part) => part.type === "source-url")
  .filter(
    (part, index, all) =>
      all.findIndex((other) => other.url === part.url) === index
  )
```

`title` is optional on a `source-url` part. Fall back to the hostname of the URL.

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace) for every part and prop.

## Accessibility

- Each source is a plain link inside the list.
- Links open in a new tab. Add `sr-only` text, such as "(opens in a new tab)", so screen readers announce it.
- The arrow icon is decorative, so the title is the name of the link.
- The count in the trigger, such as "Used 3 sources", says what opens before the list is opened.

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { ArrowUpRightIcon, GlobeIcon } from "phosphor-react-native"
import { Linking, Pressable, View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Icon } from "@/components/ui/icon"
import { Message, MessageContent } from "@/components/ui/message"
import { Text } from "@/components/ui/text"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

const sources = [
  {
    title: "Install Astro | docs.astro.build",
    url: "https://docs.astro.build/en/install-and-setup/",
  },
  {
    title: "create-astro | npmjs.com",
    url: "https://www.npmjs.com/package/create-astro",
  },
  { title: "pnpm create | pnpm.io", url: "https://pnpm.io/cli/create" },
]

export function AiSourcesExample() {
  return (
    <View className="w-full gap-4">
      <Message align="end">
        <MessageContent>
          <Bubble variant="muted">
            <BubbleContent>How do I start a new Astro project?</BubbleContent>
          </Bubble>
        </MessageContent>
      </Message>
      <TraceCollapsible>
        <TraceCollapsibleTrigger>
          <TraceLine>
            <TraceIcon>
              <Icon as={GlobeIcon} />
            </TraceIcon>
            <TraceTitle>Used 3 sources</TraceTitle>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent>
          <Trace>
            {sources.map((source) => (
              <Pressable
                key={source.url}
                accessibilityRole="link"
                onPress={() => Linking.openURL(source.url)}
                className="self-start active:opacity-70"
              >
                <TraceLine>
                  <TraceIcon>
                    <Icon as={GlobeIcon} />
                  </TraceIcon>
                  <TraceTitle>{source.title}</TraceTitle>
                  <TraceIcon>
                    <Icon as={ArrowUpRightIcon} />
                  </TraceIcon>
                </TraceLine>
              </Pressable>
            ))}
          </Trace>
        </TraceCollapsibleContent>
      </TraceCollapsible>
      <Text>Run pnpm create astro and follow the prompts.</Text>
    </View>
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct SourcesExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("How do I start a new Astro project?") }
        }
      }
      TraceCollapsible {
        TraceCollapsibleTrigger {
          TraceLine {
            TraceIcon { Ph.globe.regular }
            TraceTitle(verbatim: "Used 3 sources")
          }
        }
        TraceCollapsibleContent {
          Trace {
            AISourceRow(title: "Install Astro | docs.astro.build", url: "https://docs.astro.build/en/install-and-setup/")
            AISourceRow(title: "create-astro | npmjs.com", url: "https://www.npmjs.com/package/create-astro")
            AISourceRow(title: "pnpm create | pnpm.io", url: "https://pnpm.io/cli/create")
          }
        }
      }
      Text("Run pnpm create astro and follow the prompts.").textStyle(.base).foregroundStyle(EsuissColor.foreground)
    }
  }
}

struct AISourceRow: View {
  let title: String
  let url: String

  @Environment(\.openURL) private var openURL

  var body: some View {
    Button {
      if let link = URL(string: url) { openURL(link) }
    } label: {
      TraceLine {
        TraceIcon { Ph.globe.regular }
        TraceTitle(verbatim: title)
        TraceIcon { Ph.arrowUpRight.regular }
      }
    }
    .buttonStyle(.plain)
    .accessibilityAddTraits(.isLink)
  }
}
```

```kotlin
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.ArrowUpRight
import com.adamglin.phosphoricons.regular.Globe
import com.esuiss.ui.components.Bubble
import com.esuiss.ui.components.BubbleContent
import com.esuiss.ui.components.BubbleVariant
import com.esuiss.ui.components.Icon
import com.esuiss.ui.components.Message
import com.esuiss.ui.components.MessageAlign
import com.esuiss.ui.components.MessageContent
import com.esuiss.ui.components.Text
import com.esuiss.ui.components.Trace
import com.esuiss.ui.components.TraceCollapsible
import com.esuiss.ui.components.TraceCollapsibleContent
import com.esuiss.ui.components.TraceCollapsibleTrigger
import com.esuiss.ui.components.TraceIcon
import com.esuiss.ui.components.TraceLine
import com.esuiss.ui.components.TraceTitle

@Composable
fun SourcesAIDefault() {
    Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        Message(Modifier.fillMaxWidth(), align = MessageAlign.End) {
            MessageContent {
                Bubble(variant = BubbleVariant.Muted) { BubbleContent("How do I start a new Astro project?") }
            }
        }
        TraceCollapsible {
            TraceCollapsibleTrigger {
                TraceLine {
                    TraceIcon { Icon(PhosphorIcons.Regular.Globe, contentDescription = null) }
                    TraceTitle("Used 3 sources")
                }
            }
            TraceCollapsibleContent {
                Trace {
                    SourceRow("Install Astro | docs.astro.build", "https://docs.astro.build/en/install-and-setup/")
                    SourceRow("create-astro | npmjs.com", "https://www.npmjs.com/package/create-astro")
                    SourceRow("pnpm create | pnpm.io", "https://pnpm.io/cli/create")
                }
            }
        }
        Text("Run pnpm create astro and follow the prompts.")
    }
}

@Composable
fun SourceRow(title: String, url: String) {
    val uriHandler = LocalUriHandler.current
    TraceLine(Modifier.clickable(role = Role.Button) { uriHandler.openUri(url) }) {
        TraceIcon { Icon(PhosphorIcons.Regular.Globe, contentDescription = null) }
        TraceTitle(title)
        TraceIcon { Icon(PhosphorIcons.Regular.ArrowUpRight, contentDescription = null) }
    }
}
```

Sources on mobile is built from the same parts as on the web: [Trace](https://suiss.app/ui/docs/components/trace), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble). 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 trace message bubble
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add trace message bubble --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add trace message bubble --platform kotlin
```

## API Reference

See [Trace](https://suiss.app/ui/docs/components/trace), [Message](https://suiss.app/ui/docs/components/message), [Bubble](https://suiss.app/ui/docs/components/bubble).

