Skip to main content
NachUI

Sources

A collapsed list of the citations behind an answer, each with a favicon, a title and its host.

1'use client';
2
3import { Sources } from '@/components/ui/sources';
4
5export function Default() {
6 return (
7 <div className="w-full max-w-md">
8 <Sources>
9 <Sources.Trigger />
10 <Sources.Content>
11 <Sources.Item href="https://react.dev/reference/react/use" title="use – React" />
12 <Sources.Item
13 href="https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/"
14 title="Disclosure pattern – ARIA Authoring Practices Guide"
15 />
16 <Sources.Item
17 href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"
18 title="<details>: The Details disclosure element – MDN"
19 />
20 </Sources.Content>
21 </Sources>
22 </div>
23 );
24}

Installation

pnpm dlx nachui add sources

Anatomy

1import { Sources } from '@/components/ui/sources';
1<Sources>
2 <Sources.Trigger />
3 <Sources.Content>
4 <Sources.Item href="https://react.dev/reference/react/use" title="use – React" />
5 <Sources.Item href="https://www.w3.org/WAI/ARIA/apg/" title="ARIA Authoring Practices" />
6 </Sources.Content>
7</Sources>
Sources start closed. The trigger counts the items on its own and reads "Used 2 sources", so the answer keeps its shape and the citations sit one click away.

Composition

Use the following composition to build Sources:
Sources
├── Sources.Trigger
└── Sources.Content
└── Sources.Item

Open by default

Pass defaultOpen when the citations are the point, for example in a research view. Each item derives its host from the href and pulls a favicon for it, and favicon overrides that image or removes it with null.
1'use client';
2
3import { Sources } from '@/components/ui/sources';
4
5export function Open() {
6 return (
7 <div className="w-full max-w-md">
8 <Sources defaultOpen>
9 <Sources.Trigger />
10 <Sources.Content>
11 <Sources.Item href="https://nextjs.org/docs/app" title="App Router – Next.js" />
12 <Sources.Item
13 href="https://tailwindcss.com/docs/theme"
14 title="Theme variables – Tailwind"
15 />
16 <Sources.Item
17 href="https://motion.dev/docs/react-animate-presence"
18 title="AnimatePresence – Motion"
19 />
20 <Sources.Item href="https://vitest.dev/guide/" title="Getting started – Vitest" />
21 <Sources.Item
22 href="https://www.typescriptlang.org/tsconfig/"
23 title="tsconfig reference – TypeScript"
24 />
25 </Sources.Content>
26 </Sources>
27 </div>
28 );
29}

Labels

label on the trigger receives the count and returns the text, which is where translations go. Give the trigger children instead to replace the label entirely. When the number of items is not the number of citations, say because the list is paginated, set count on the root.

API Reference

Sources

PropTypeDefaultDescription
defaultOpenbooleanfalseOpen state when uncontrolled
openboolean-Controlled open state
onOpenChange(open: boolean) => void-Called when the open state changes
countnumber-Number shown in the trigger, defaults to items
classNamestring-Additional CSS classes

Sources.Trigger

PropTypeDefaultDescription
label(count: number) => stringUsed N sourcesBuilds the label from the count
childrenReactNode-Replaces the label when given
classNamestring-Additional CSS classes

Sources.Content

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Sources.Item

PropTypeDefaultDescription
hrefstring-Link to the source, opens in a new tab
titlestring-Text of the citation
faviconstring | nullGoogle faviconImage next to the title, null hides it
childrenReactNode-Replaces the title when given
classNamestring-Additional CSS classes

sourceHost

sourceHost(href) returns the hostname of a URL without its www. prefix, and the input unchanged when it is not a URL.
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue