Skip to main content

UI Primitives

Low-level primitive components with no business logic and no dependency on app state or domain types. Each is a focused, composable building block modelled after the shadcn/ui pattern. Compose them into higher-level components in components/; snippets should not import directly from ui/ unless the usage is trivially simple.

Available Primitives

Component(s)Directory / FilePurpose
Button, ButtonWithTooltipui/button/Standard button and a button with an attached tooltip
Txtui/Txt.svelteTypography primitive with a semantic variant prop
Dialog, ConfirmDialog, InfoDialogui/dialog/Modal dialogs — generic, confirm-action, and informational variants
DropdownMenu + itemsui/dropdown-menu/Full dropdown composition: groups, separators, checkbox/radio/switch items, detail view
Popover, InfoPopoverui/popover/Floating popover and a pre-styled info variant
SingleSelectui/select/Styled single-value select input
BottomSheetui/sheet/Mobile-friendly bottom drawer
Sliderui/slider/Range input slider
Switchui/switch/Toggle switch
Tabsui/tabs/Tab navigation
Tooltipui/tooltip/Floating tooltip
Toaster + ToastContextui/toast/Toast notification system — see below
Badgeui/badge/Label/badge chip
RadialProgressui/radial-progress/Circular progress indicator
BorderBeamui/border-beam/Animated border highlight effect
StatusDotui/status-dot/Colored status indicator dot
Separatorui/separator/Visual divider line
RadioCard, RadioCardGroupui/radio-card/Card-style radio group — each card is selectable with a spring-animated indicator
Citation, CitationList, CitationReference, CitationRootui/citations/Web-search citation tiles and inline reference chips rendered below AI messages

Toasts

The toast system consists of two parts: the Toaster component (rendered once by LegacySharedContent.svelte) and ToastContext, which any component uses to push notifications.

<script lang="ts">
import {useToastContext} from '$lib/components/ui/toast/ToastContext.svelte.js';
const toast = useToastContext();
</script>

<button onclick={() => toast.success('Saved!')}>Save</button>
<button onclick={() => toast.error('Something went wrong.')}>Fail</button>
<button onclick={() => toast.info('Processing…')}>Info</button>

ToastContext is set up by the LegacySharedContent snippet which is auto-injected on every page. Do not instantiate Toaster yourself.


RadioCard

RadioCardGroup + RadioCard implement a card-style radio group. Bind value on the group; each card's value prop identifies it.

<script lang="ts">
import RadioCardGroup from '$lib/components/ui/radio-card/RadioCardGroup.svelte';
import RadioCard from '$lib/components/ui/radio-card/RadioCard.svelte';

let selected = $state('a');
</script>

<RadioCardGroup bind:value={selected} name="my-group">
<RadioCard value="a">Option A</RadioCard>
<RadioCard value="b">Option B</RadioCard>
<RadioCard value="c" disabled>Option C (disabled)</RadioCard>
</RadioCardGroup>

RadioCardGroup props:

PropTypeDefaultDescription
valuestring''The selected card's value. Bindable.
disabledbooleanfalseDisables (and dims) every card in the group.
namestringShared name for the underlying radio inputs.
onChange(value: string) => voidCalled with the newly selected value.

RadioCard props:

PropTypeDefaultDescription
valuestringThe value this card represents.
disabledbooleanfalseDisables this card individually.
childrenSnippetCard content.

Selection is animated with a spring-driven dot indicator. Cards are keyboard-reachable with Space/Enter and carry full ARIA role="radio" / role="radiogroup" semantics.


Citations

The citation system renders web-search sources below an AI message as a grid of tiles, and wires inline numbered chips in the message body to scroll and flash-highlight the matching tile.

Four components work together:

ComponentRole
CitationRootWraps the entire message + citation area; sets up the shared CitationContext.
CitationListRenders the "Sources" heading and the tile grid. Place it after the message body.
CitationA single source tile — displays favicon, domain, and source number; scrolls and flashes when its chip is clicked.
CitationReferenceAn inline chip (used inside rendered markdown) that scrolls to the matching Citation tile when clicked.

injectCitationsIntoMarkdown (in $lib/components/chat/message/injectCitationsIntoMarkdown.ts) pre-processes a markdown string and rewrites citation ranges into anchor links that ExtendedLinkNode turns into CitationReference chips.

Typical assembly:

<CitationRoot>
<!-- Rendered message body (uses ExtendedLinkNode via Markdown component) -->
<Markdown message={body} />

<!-- Source tiles -->
<CitationList>
{#each citations as citation, i}
<Citation {citation} number={i + 1} />
{/each}
</CitationList>
</CitationRoot>

Citation expects an EnrichedUrlCitation ({ url, title, ranges, identifier } from $lib/components/ui/citations/types.js). The identifier field is the stable key that links a tile to its inline chips.


Adding a New Primitive

When porting or writing a new primitive:

  1. Create a directory in components/ui/ named after the component (kebab-case).
  2. Follow the patterns in Writing Svelte ComponentsProps extends HTMLAttributes<…>, mergeProps for rest-prop forwarding, @component block comment.
  3. Build on bits-ui primitives where one fits (dialogs, popovers, selects, tooltips, etc.) — they handle accessibility and keyboard navigation.
  4. Style with the CSS token system described in Styling. No Tailwind, no hard-coded values.