Sources

Sources show where an answer or lesson comes from: citation cards, a stack of site icons, and file rows. Use them next to AI answers and lesson content.

  • Beta
  • Accessibility
  • Figma References 3852:44840 · Links 3850:35433 · Source file item 3842:32371
  • packages/ui/src/components/Source

Beta until design confirms the code-only focus, pressed and disabled styles, the favicon fallback and the selected-row behaviour, and the components are checked in a browser at 375px.

Preview

Pick a component and change its options. The favicons here are neutral placeholders made by the docs; apps pass real favicon URLs.

openbiology.exampleCellular respiration: how cells release energy (opens in a new tab)Cellular respiration turns glucose and oxygen into usable energy (ATP), water and carbon dioxide. Most of it happens in the mitochondria.

tsx
import { SourceCard } from '@nuton/ui'

<SourceCard
  href="https://openbiology.example/respiration"
  title="Cellular respiration: how cells release energy"
  site="openbiology.example"
  favicon={faviconUrl}
  excerpt="Cellular respiration turns glucose and oxygen into usable energy (ATP), water and carbon dioxide. Most of it happens in the mitochondria."
/>

Installation

Availability

@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.

1. Add the packages

bash
pnpm add @nuton/ui @nuton/tokens

Peer dependencies: react and react-dom 18 or later. No brand images ship with the package: your app supplies favicons (D12).

2. Load tokens and component styles once

tsx
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css'     // component styles (uses the tokens)

SourceItem uses the FileFormatIcon styles, which ship in the same stylesheet.

Usage

tsx
import { SourceCard, SourceItem, SourceStack } from '@nuton/ui'

export function AnswerSources({ sources, onOpen }) {
  return (
    <>
      <SourceStack sources={sources} onClick={onOpen} />
      {sources.map((s) => (
        <SourceCard key={s.url} href={s.url} title={s.title} site={s.domain} favicon={s.faviconUrl} excerpt={s.quote} />
      ))}
      <SourceItem name="Form and Function in Design.pdf" onClick={() => openFile('form-and-function')} />
    </>
  )
}
  • A SourceCard links from its title, which covers the whole card. It opens in a new tab by default and says so to screen readers; set newTab={false} for sources inside Nuton.
  • SourceStack summarises many sources in one small control. Give it onClick to open the full list, and wire aria-expanded when it shows and hides them.
  • SourceItem is a file row. Pass selected when the row picks which file is shown, so it reads as a toggle.
  • Pass favicons as URLs from your source data. When one is missing or fails, a neutral globe shows instead.
  • Put cards and items in a list (<ul>) so people hear how many there are.

Variants and sizes

Three components, each with one size.

Source card

openbiology.exampleCellular respiration: how cells release energy (opens in a new tab)Cellular respiration turns glucose and oxygen into usable energy (ATP), water and carbon dioxide. Most of it happens in the mitochondria.

Source stack

Source item

Sizes

PartSizeToken or source
Source cardPadding 16, radius 16, gap 4, 1px border--nu-source-card-pad, --nu-radius-16
Card favicon and site16 circle with a 2px white ring; site Label/X Small 12/20, gap 6--nu-source-favicon-size, --nu-type-label-x-small-*
Card title and excerptLabel/Small 14/20; Paragraph/Small 14/24, 2 lines--nu-type-label-small-*, --nu-type-paragraph-small-*
Source stackPadding 4, 20 icons overlapping by 4, “+N” chip 20 wide (grows for two digits). 28 tall.--nu-source-stack-size, --nu-source-stack-overlap
Source itemPadding 4, gap 8, radius 8; 24 icon tile with a 20 badge; name Label/Small. 32 tall.--nu-source-item-*, FileFormatIcon size={24}

Values from the component CSS, which follows Figma's geometry (References 3852:44840, Links 3850:35433, source file item 3842:32371).

States and interactions

classwiki.exampleGlycolysis step by stepGlycolysis splits one glucose molecule into two pyruvate molecules.
Card default and hoverBg/White; hover turns Bg/Weak-50 (Figma Variant2). Point at it.
StackFigma Default. Without onClick it is a labelled image, not a control.
Item default and hoverTransparent; hover Bg/Weak-50.
Item activeBg/Soft-100 (Figma Active), with aria-pressed="true".
Focus (code-only)A 2px primary outline: outside cards and stacks, inside item rows so lists don't overlap.
Disabled item (code-only)Text/Disabled-500 (4.23:1, C8). Prefer hiding sources people can't open.
No favicon (code-only)A globe icon on slate/200 replaces a missing or broken image.

State tokens

TokenValueUsed for
--nu-source-card-bg / -bg-hoverBg/White / Bg/Weak-50Card, hover and pressed
--nu-source-item-bg-hoverBg/Weak-50Item hover and pressed
--nu-source-item-bg-selectedBg/Soft-100Item active (selected)
Stack hoverBg/Soft-200Clickable stack, hover and pressed

Composition examples

AI answer with sources

The stack sits under the answer. Pressing it shows the source cards and sets aria-expanded, so screen readers know the list opened.

Mitochondria release energy from food through cellular respiration. Glucose is broken down in stages (glycolysis, the Krebs cycle and the electron transport chain) to make ATP, the energy the cell uses.

Lesson source files

A list of the files a lesson was built from. Choosing one shows it; the chosen row is pressed.

Showing: Form and Function in Design.pdf

API reference

SourceCard props

SourceCard props
PropTypeDefaultDescription
hrefrequiredstring—URL of the source.
titlerequiredReactNode—Source title. Names the link.
siteReactNode—Site name or domain above the title. Part of the description.
faviconstring—Favicon URL. Decorative. A globe shows when it is missing or fails.
excerptReactNode—Quote or summary. Part of the description.
excerptLinesnumber2Lines before the ellipsis (1–4). 0 shows all.
newTabbooleantrueOpens in a new tab with rel="noopener noreferrer" and a hidden hint.
labelsPartial<SourceCardLabels>{ newTab: '(opens in a new tab)' }Overrides for the hint text.
refRef<HTMLAnchorElement>—Forwarded to the link.
…restOmit<AnchorHTMLAttributes, 'title' | 'children'>—Any other link attribute. target and rel apply only with newTab={false}.

SourceStack props

SourceStack props
PropTypeDefaultDescription
sourcesrequiredreadonly { favicon?: string; name?: string; id?: string }[]—The sources. The first max favicons show.
maxnumber4Favicons shown before the "+N" count.
totalnumbersources.lengthTotal count when it is more than the sources passed.
onClickMouseEventHandler<HTMLButtonElement>—Makes the stack a button. Without it, it is role="img".
labelsPartial<SourceStackLabels>{ count: n => 'n sources', more: 'more' }The accessible name: the total, then 'more' after the visible +N on a button.
refRef<HTMLElement>—Forwarded to the button or span.
…restOmit<ButtonHTMLAttributes, 'children'>—Spread on the root, e.g. aria-expanded and aria-controls.

SourceItem props

SourceItem props
PropTypeDefaultDescription
namerequiredstring—File name. Names the button, sets the tooltip and the format icon.
formatFileFormatTypefrom nameFormat icon type (pdf, doc, link…).
selectedboolean—Figma Active. Sets aria-pressed. Leave undefined for a plain action row.
trailingReactNode—Extra text after the name, e.g. "12 pages". Part of the name.
disabledbooleanfalseNative disabled (code-only style).
refRef<HTMLButtonElement>—Forwarded to the button.
…restOmit<ButtonHTMLAttributes, 'children'>—Any other button attribute.

Styling hooks

.nu-source-card (__site, __title, __excerpt[data-lines]), .nu-source-stack ([data-interactive], __more), .nu-source-item([data-selected], __name) and the shared .nu-source-favicon. Override with the --nu-source-* tokens.

Accessibility

Keyboard

KeyBehaviour
TabMoves to each card, clickable stack and item.
EnterOpens a card link; activates a stack or item.
SpaceActivates a stack or item (buttons).

Screen readers

A card's title is the link, stretched over the whole card, so its name is exactly the visible title plus “(opens in a new tab)”; the site and excerpt describe it. A stack image is “13 sources”. A stack button is “13 sources, +9 more”, so the visible “+9” is part of its name for voice control (WCAG 2.5.3). An item is a button named by the file name; with selected it is announced as pressed or not pressed. Favicons and format icons are decorative.

Focus

A 2px primary outline. Cards and stacks put it 2px outside; item rows draw it inside, so rows in a tight list don't overlap.

Contrast and colour

Titles are 17.9:1, site names and excerpts 6.7:1 on white (6.2:1 on the hover fill). The “+N” text is 6.2:1 on Bg/Weak-50. Selection is also exposed as aria-pressed, not only by the fill.

Touch and pointer

Cards are large targets. The stack is 28 tall and items 32 tall, above the 24px minimum. Hover styles apply only on hover-capable pointers.

You must: pass a real title for every card (not the URL); keep aria-expanded in sync when a stack shows and hides sources; translate labels, including the new-tab hint and the source count.

Figma mapping

Figma componentFigma valuesCode
References · Property 1Default · Variant2SourceCard; Variant2 is the hover fill
Links · Property 1Default · HoverSourceStack; hover only with onClick
Source file item · Property 1Default · Hover · ActiveSourceItem; Active is selected
File Format (PDF)24 tile, 20 badgeFileFormatIcon size={24} with a 20 badge

Guidelines and related components

Use sources for

  • Citations behind an AI answer or a generated lesson.
  • The files and links a lesson or space was built from.

Don't use them for

Do and don't

DoUse the page title. A clear title tells people what they will open before they leave the lesson.
Don'tDon't use the URL as the title. Long URLs are hard to read and slow to hear.
DoSummarise many sources with a stack. The answer stays readable, and people open the list when they want it.
Don'tDon't line up every source under a short answer. A wall of cards pushes the answer out of view.

Common mistakes

  • Putting buttons or other links inside a SourceCard. The title link covers the whole card.
  • Giving a stack an onClick but no visible result, or not updating aria-expanded.
  • Using selected on rows that open a file in a new view instead of choosing what is shown.
  • Shipping brand logos as favicons in the design system. Apps fetch them from the source.