Source

A small link chip with a favicon and a label for a page the model searched, read, or cited. Use it in a reasoning step to show what was read, or inline after a claim with a card that previews the cited pages.

<script lang="ts">
    import * as Source from '@sivir-ui/svelte/components/source';

    const sources = [
        {
            domain: 'nngroup.com',
            href: 'https://www.nngroup.com/articles/response-times-3-important-limits/',
            icon: '/favicons/nngroup.com.png'
        },
        {
            domain: 'developer.mozilla.org',
            href: 'https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame',
            icon: '/favicons/developer.mozilla.org.png'
        },
        {
            domain: 'web.dev',
            href: 'https://web.dev/articles/optimize-inp',
            icon: '/favicons/web.dev.png'
        }
    ];
</script>

<div class="flex max-w-xl flex-wrap gap-1.5">
    {#each sources as source (source.href)}
        <Source.Root href={source.href}>
            <Source.Icon src={source.icon} fallback={source.domain} />
            <Source.Label>{source.domain}</Source.Label>
        </Source.Root>
    {/each}
</div>

Installation

Copy the command below and run it in your terminal.

bunx @sivir-ui/svelte add source

Usage

Root is a link, so href is required. Absolute http and https links open in a new tab unless you set target. Icon shows the favicon from src, which can be any image URL, such as one your search backend returns, and Label truncates long names.

import * as Source from '@sivir-ui/svelte/components/source';

<Source.Root href="https://web.dev/articles/optimize-inp">
  <Source.Icon src="/favicons/web.dev.png" fallback="web.dev" />
  <Source.Label>web.dev</Source.Label>
</Source.Root>

Citations

Place Root inside the paragraph, right after the sentence it supports. There the chip tightens to sit on the text line. Add a Content and the chip opens it on hover and focus. Put one Item in it per cited page, each with an Icon, Label, Title, and an optional Description. Content renders only in the browser, so it is safe inside a <p>.

<p>
  Keep the main thread free so input stays responsive.
  <Source.Root href="https://web.dev/articles/optimize-inp">
    <Source.Icon src="/favicons/web.dev.png" fallback="web.dev" />
    <Source.Label>web.dev</Source.Label>
    <Source.Content>
      <Source.Item href="https://web.dev/articles/optimize-inp">
        <Source.Icon src="/favicons/web.dev.png" fallback="web.dev" />
        <Source.Label>web.dev</Source.Label>
        <Source.Title>Optimize Interaction to Next Paint</Source.Title>
        <Source.Description>Keeping the main thread free.</Source.Description>
      </Source.Item>
    </Source.Content>
  </Source.Root>
</p>

Examples

Missing icons

When src is missing or fails to load, the icon shows the first letter of fallback, or a globe without one.

<script lang="ts">
    import * as Source from '@sivir-ui/svelte/components/source';
</script>

<div class="flex max-w-xl flex-wrap gap-1.5">
    <Source.Root href="https://example.com/handbook">
        <Source.Icon fallback="Handbook" />
        <Source.Label>Handbook</Source.Label>
    </Source.Root>
    <Source.Root href="https://example.com/status">
        <Source.Icon />
        <Source.Label>status.example.com</Source.Label>
    </Source.Root>
</div>

Inline citation

Hover or focus the chip to preview the cited page.

People notice a delay after about a second and lose focus after ten, so a model that reasons for several seconds needs feedback that changes. Nielsen Norman Group

<script lang="ts">
    import * as Source from '@sivir-ui/svelte/components/source';
</script>

<p class="m-0 max-w-xl leading-body text-foreground">
    People notice a delay after about a second and lose focus after ten, so a model that reasons for
    several seconds needs feedback that changes.
    <Source.Root href="https://www.nngroup.com/articles/response-times-3-important-limits/">
        <Source.Icon src="/favicons/nngroup.com.png" fallback="nngroup.com" />
        <Source.Label>Nielsen Norman Group</Source.Label>
        <Source.Content>
            <Source.Item href="https://www.nngroup.com/articles/response-times-3-important-limits/">
                <Source.Icon src="/favicons/nngroup.com.png" fallback="nngroup.com" />
                <Source.Label>nngroup.com</Source.Label>
                <Source.Title>Response Times: The 3 Important Limits</Source.Title>
                <Source.Description>
                    The classic 0.1, 1 and 10 second limits for keeping attention and a sense of
                    control while a system works.
                </Source.Description>
            </Source.Item>
        </Source.Content>
    </Source.Root>
</p>

Multiple sources

Set count on Root to the number of cited pages, and Count shows how many follow the first, such as +1. Count reads the prop, because the card's items are not mounted until it opens.

Drive the motion from the browser’s frame loop so the indicator and the text stay smooth without blocking input. MDN and 1 more source

<script lang="ts">
    import * as Source from '@sivir-ui/svelte/components/source';

    const sources = [
        {
            domain: 'developer.mozilla.org',
            href: 'https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame',
            icon: '/favicons/developer.mozilla.org.png',
            title: 'Window: requestAnimationFrame() method',
            description:
                'Schedules work right before the next repaint, the standard way to run smooth, frame-synced animation.'
        },
        {
            domain: 'web.dev',
            href: 'https://web.dev/articles/optimize-inp',
            icon: '/favicons/web.dev.png',
            title: 'Optimize Interaction to Next Paint',
            description:
                'Keeping the main thread free so the page answers clicks, taps and typing quickly.'
        }
    ];

    const [first] = sources;
</script>

<p class="m-0 max-w-xl leading-body text-foreground">
    Drive the motion from the browser’s frame loop so the indicator and the text stay smooth without
    blocking input.
    <Source.Root href={first.href} count={sources.length}>
        <Source.Icon src={first.icon} fallback={first.domain} />
        <Source.Label>MDN</Source.Label>
        <Source.Count />
        <Source.Content>
            {#each sources as source (source.href)}
                <Source.Item href={source.href}>
                    <Source.Icon src={source.icon} fallback={source.domain} />
                    <Source.Label>{source.domain}</Source.Label>
                    <Source.Title>{source.title}</Source.Title>
                    <Source.Description>{source.description}</Source.Description>
                </Source.Item>
            {/each}
        </Source.Content>
    </Source.Root>
</p>

Anatomy

Source.Root - Links to the page as a compact chip and holds the hover state and count.

Source.Icon - Shows the favicon or a fallback.

Source.Label - Renders the truncated site name.

Source.Count - Shows how many sources follow the first.

Source.Content - Opens a hover card of the cited pages.

Source.Item - Links one cited page and lays out its parts.

Source.Title - Renders the page title.

Source.Description - Renders a two-line excerpt.