Components

The live style guide is the authoritative component reference. Every component ships with a runnable showcase route under /components/* that demonstrates props, variants, and rendered output side-by-side with the Vue source.

Run the style guide:

npm run dev
# → http://localhost:3030/components

What ships today

ComponentWrapsRoute
TuxAccordiontux native/components/accordion
TuxActivityTimelinetux native/components/activity-timeline
TuxAlertUAlert/components/alert
TuxAlphaNavtux native/components/alpha-nav
TuxAnnouncementBannertux native/components/announcement-banner
TuxAvatartux native/components/avatar
TuxAppFrametux native/components/app-frame
TuxAppSwitcherUPopover/components/app-switcher
TuxArtifacttux native/components/artifact
TuxBadgeUBadge/components/badge
TuxBetaRibbontux native/components/beta-ribbon
TuxBigStattux native/components/big-stat
TuxBlockquotetux native/components/blockquote
TuxBranchNavtux native/components/branch-nav
TuxBreadcrumbstux native/components/breadcrumbs
TuxButtonUButton/components/button
TuxCallouttux native/components/callout
TuxCaptionedMediatux native/components/captioned-media
TuxCardtux native/components/card
TuxCardCarouselUCarousel (embla) wrap/components/card-carousel
TuxCardSlabtux native/components/card-slab
TuxCookieConsenttux native/components/cookie-consent
TuxChatMessagetux native/components/chat-message
TuxCitationstux native/components/citations
TuxCodeBlockShiki/components/code-block
TuxCodeMaroontux native/components/code-maroon
TuxCommandPalettetux native/components/command-palette
TuxCommentThreadtux native/components/comment-thread
TuxContactCardtux native/components/contact-card
TuxContextMeterUPopover/components/context-meter
TuxContextPaneltux native/components/context-panel
TuxConversationListtux native/components/conversation-list
TuxComposertux native/components/composer
TuxCTAtux native/components/cta
TuxDataTabletux native/components/data-table
TuxDescriptionListtux native/components/description-list
TuxDiagramMermaid/components/diagram
TuxDocsSidebartux native/components/docs-sidebar (renders internal child TuxDocsSidebarNode)
TuxDropdowntux native/components/site-nav
TuxEmptyStateTuxCard composite/components/empty-state
TuxErrorPagetux native/components/error-page
TuxExampleshowcase primitive(used on every component page)
TuxFABtux native/components/fab
TuxFactoidtux native/components/factoid
TuxFilterPaneltux native/components/filter-panel
TuxFocusViewtux native/components/focus-view
TuxFootertux native/components/footer
TuxIconFeaturetux native/components/icon-feature
TuxIdentitytux native/components/identity
TuxInfoLabelUPopover/components/info-label
TuxInlineCitationUPopover/components/inline-citation
TuxKbdtux native/components/kbd
TuxLinkListtux native/components/link-list
TuxLinkSlabtux native/components/link-slab
TuxMcpEmbedtux native/components/mcp-embed
TuxMediaSlabtux native/components/media-slab
TuxMegaMenutux native/components/site-nav
TuxMenuBarUDropdownMenu/components/menu-bar
TuxMobileFrametux native (CSS)/components/mobile-frame
TuxModalUModal/components/modal
TuxNewsCollectiontux native/components/news-collection
TuxPageContainertux native/components/page-container
TuxPageHeadertux native/components/page-header
TuxPaginationtux native/components/pagination
TuxPhotoGridtux native/components/photo-grid
TuxPopoverUPopover/components/popover
TuxProsetux native/components/prose
TuxQACollectiontux native/components/qa-collection
TuxRailNavtux native (<details>)/components/rail-nav
TuxRemovableChiptux native/components/removable-chip
TuxRichDataGridtux native/components/rich-data-grid
TuxRichTextEditorTiptap + lowlight/components/rich-text-editor
TuxRuleBuildertux native/components/rule-builder (renders internal child TuxRuleBuilderGroup recursively)
TuxSearchtux native/components/search
TuxSectionHeadertux native/components/section-header
TuxShortcutsHelptux native (<dialog>)/components/shortcuts-help
TuxSidebarBlocktux native/components/sidebar-block
TuxSignupFeaturetux native/components/signup-feature
TuxSiteNavtux native/components/site-nav
TuxSkeletontux native/components/skeleton
TuxSlideovertux native/components/slideover
TuxSplashScreentux native/components/splash-screen
TuxSplitPanetux native/components/split-pane
TuxStatusToasttux native (useTuxToast bus)/components/status-toast
TuxSteppertux native/components/stepper
TuxStatComparisontux native/components/stat-comparison
TuxSuggestionChipstux native/components/suggestion-chips
TuxTableUTable/components/table
TuxTabBartux native/components/tab-bar
TuxTabsUTabs/components/tabs
TuxTeachingPopovertux native/components/teaching-popover
TuxTestimonialtux native/components/testimonial
TuxTOCtux native/components/toc
TuxTooltipUTooltip/components/tooltip
TuxTreetux native/components/tree (renders internal child TuxTreeNode)
TuxTreemaptux native/components/treemap
TuxUserMenuUDropdownMenu/components/utility-cluster
TuxUtilityClustercomposes TuxAppSwitcher + TuxUserMenu/components/utility-cluster

Pagination + result-display family (added 2026-05-21):

ComponentStackShowcase
TuxResultCounttux native/components/result-count
TuxLoadMoretux native/components/load-more
TuxInfiniteScrolltux native/components/infinite-scroll
TuxReactionBartux native/components/reaction-bar

Research-publishing family (added 2026-05-22):

ComponentWrapsShowcase
TuxAbstracttux native/components/research-publishing
TuxAuthorBylinetux native/components/research-publishing
TuxPaperMetatux native/components/research-publishing
TuxFigureCaptiontux native/components/research-publishing
TuxTableCaptioncomposes TuxFigureCaption/components/research-publishing
TuxFootnoteUPopover/components/research-publishing
TuxCitationExportUDropdownMenu/components/research-publishing
TuxAcknowledgmentstux native/components/research-publishing

TTI identity family (added 2026-05-22):

ComponentWrapsShowcase
TuxResearchertux native/components/tti-identity
TuxLabtux native/components/tti-identity
TuxProgramtux native/components/tti-identity
TuxFundingSourcetux native/components/tti-identity
TuxCenterBadgetux native/components/tti-identity

Geospatial family (added 2026-05-22):

ComponentWrapsShowcase
TuxMapEmbediframe / slot/components/geospatial
TuxCorridorStriptux native SVG/components/geospatial
TuxMapLegendtux native/components/geospatial
TuxMapMarkertux native SVG/components/geospatial

Forms wrapper family (added 2026-05-22):

ComponentWrapsShowcase
TuxFormFieldtux native/components/forms-wrapper
TuxMarkdownEditortux native (no deps)/components/forms-wrapper
TuxFileDropzonetux native/components/forms-wrapper
TuxValidationSummarytux native/components/forms-wrapper
TuxConfirmDialogTuxModal preset/components/forms-wrapper

Native chart family (Priority B — closed 2026-05-22):

ComponentStackShowcase
TuxChartLinetux native SVG/visualizations/chart-line
TuxChartBartux native SVG/visualizations/chart-bar
TuxChartAreatux native SVG/visualizations/chart-area
TuxChartDonuttux native SVG/visualizations/chart-donut
TuxChartScattertux native SVG/visualizations/chart-scatter
TuxChartGaugetux native SVG/visualizations/chart-gauge
TuxChartHeatmaptux native SVG/visualizations/chart-heatmap
TuxChartHistogramtux native SVG/visualizations/chart-histogram

Reports section

Finished-narrative deliverables — paper, PDF, print, or web-hosted long-form. The reader reads top-to-bottom; they don't pivot or filter. Components live in app/components/ like everything else, but demos live under /reports/<kebab> (with a landing at /reports).

ComponentWrapsOutput mediumRoute
TuxReportFrametux nativepaper · PDF/reports/frame
TuxReportPrintSheetuseHead injectionprint stylesheet/reports/print-sheet
TuxReportWebFrametux nativeweb (HTML page)/reports/web-frame

Visualizations section

Interactive data surfaces — BI dashboards, R artifacts, native charts. The reader pivots, filters, drills in. Same pattern as Reports (flat components, grouped routes), but a different section because the chrome and posture are different (provider chip, sandbox, source caption).

ComponentWrapsRoute
TuxVizEmbedsandboxed <iframe> + poster fallback/visualizations/embed
TuxVizRPlot<img> / <object> / <iframe>/visualizations/rplot
TuxVizGridtux native (CSS Grid layout shell)/visualizations/grid
TuxSparklinetux native (inline SVG)/visualizations/sparkline
TuxChartFrametux native (editorial wrapper)(used by /visualizations/* showcase pages)
TuxChartGeographictux native (5-kind Texas map)/visualizations/chart-geographic
TuxChartSunbursttux native (two-ring radial)/visualizations/chart-sunburst
TuxMetroInsettux native (neighborhood grid)(used by /visualizations/chart-geographic)

See ADR-0008 for the positioning rationale (why data-display stays flat in /components/, why Reports and Visualizations get their own sections, and the criteria for adding a future route group).

The showcase pages expose Vue (template source), HTML (rendered DOM), and — where applicable — Source (the component SFC) tabs via the TuxExample primitive.

Platform-aware chrome family (added 2026-05-22):

Component / composableWrapsRoute
TuxAppFrametux native/components/app-frame
TuxAppSwitcherUPopover/components/app-switcher
TuxFocusViewTeleport + native/components/focus-view
TuxMenuBarUDropdownMenu/components/menu-bar
TuxSplashScreentux native/components/splash-screen
TuxSplitPanetux native/components/split-pane
TuxTabBartux native/components/tab-bar
TuxFABtux native/components/fab
useTuxPlatformcomposable(consumed by all of the above)
useTuxSwipecomposable/components/swipe
useTuxRipplecomposable/components/ripple

These ship together under the platform-aware banner. They cover the brand-vs-chrome split documented in platform-awareness.md: one component tree, platform-adaptive at the chrome layer. The tux-scrollbar.css utility ships alongside (auto-imports via globals.css) and branches scrollbar styling per host via the data-platform attribute set by useTuxPlatform().

Pattern coverage — "want X? use Y"

If you're about to build something, check this map first. The catalog covers more than the file names suggest, and shipping a duplicate component because the existing one had a non-obvious name is the single most common failure mode.

Want…Use…
Tag (mono-font label chip — topic:safety, pii:us_ssn)<TuxBadge kind="tag">
Status pill (live/running/failed/queued + dot)<TuxBadge :status="…">
Classification tier (Public / Internal / Restricted / ITAR)<TuxBadge :tier="…">
Count badge (md (11) facet count)<TuxBadge kind="count" :count="…">
Page-level admonition (Docusaurus-style note/tip/warning/danger)<TuxAlert variant="…"> — 8 variants
Compliance alert (ITAR / export-controlled / legal callout)<TuxAlert variant="compliance">
Inline body callout (single-paragraph aside in flowing prose)`<TuxCallout kind="fact
Standalone pull quote (focal element, attribution + rules)<TuxBlockquote>
Long-form Q&A (always-expanded explainer prose)<TuxQACollection>
FAQ (collapsible question/answer for scanning)<TuxAccordion kind="faq">
Code block in a doc / blog / ADR<TuxCodeBlock>
Code in a component-demo flow (Vue + HTML reveal tabs)<TuxExample>
Term/definition list (event details, file metadata, spec list)<TuxDescriptionList>
Architecture diagram (boxes + arrows, decision flows)<TuxDiagram> (Mermaid)
Markdown content with Tux components inline@nuxtjs/mdc + auto-import (see /markdown demo)
Form input (email / select / radio / etc.)Nuxt UI native — UInput, USelect, etc., layer-themed (Batch K in tux.css: Work Sans controls, tux radius, maroon focus). The hybrid doctrine (unification-plan.md, owner-ratified 2026-07-30): U* is the blessed spelling for app controls — no Tux input wrappers ship; TuxFormField owns label/help/error anatomy; TuxButton is the editorial button (gold-bar/CTA contexts), not the app-control default. Lint targets raw <input>/<button> elements and hex literals, never U*. See /forms.
Data table (sortable, virtualizable, status cells)<TuxTable>
Sortable / selectable / expandable data grid (Landscape-class operational lists with bulk actions, active-filter chips, row expansion)<TuxRichDataGrid>
Static research table (numbered caption, ± CI uncertainty, footnotes, source citation, optional totals row)<TuxDataTable>
Search bar (Landscape finder, conversation search)<TuxSearch>
A–Z directory jump bar<TuxAlphaNav>
Sidebar widget wrapper (related links, contact box, in-page nav)<TuxSidebarBlock>
Newsletter signup<TuxSignupFeature>
Big oversized stat (single headline metric)<TuxBigStat>
Row of oversized stats ("by the numbers" 3/4/5-up)<TuxFactoid>
Tiny inline trend (no axes, beside a stat)<TuxSparkline>
Loading placeholder (cards, lists, tables, articles, stats)<TuxSkeleton kind="…">
404 / 500 / 403 / 503 page<TuxErrorPage code="…">
Multi-step flow indicator (funding application, IRB, study onboarding)<TuxStepper :steps :current-index>
Site-wide notice strip (closure, maintenance, security advisory)<TuxAnnouncementBanner>
"This isn't production" label (preview env, public beta, sandbox)<TuxBetaRibbon variant="…">
Privacy / cookie consent (public TTI surface)<TuxCookieConsent>
Quarterly / sponsor PDF report (paper, print, PDF export)<TuxReportFrame>
Web-hosted long-form report (annual report, findings page at a permanent URL)<TuxReportWebFrame>
"Print this page" affordance on an existing screen<TuxReportPrintSheet>
BI dashboard embed (Tableau, Power BI, Superset, Grafana)<TuxVizEmbed>
R / ggplot artifact (PNG, SVG, htmlwidget)<TuxVizRPlot>
Side-by-side dashboard tiles (small-multiples 2/3/4-up)<TuxVizGrid>
Texas-flavored map (county choropleth, TxDOT districts, in-state dot density, OD flow arcs, AlbersUsa context)<TuxChartGeographic kind="…">
Multi-metro inset grid (4-up neighborhood drill-down — Houston / DFW / Austin / SAT)<TuxMetroInset>
Two-ring radial breakdown (sister to treemap; categorical part-to-whole with center total)<TuxChartSunburst>
Time-of-day / matrix intensity (crashes by day × hour, corridor demand by month, station uptime)<TuxChartHeatmap>
Distribution of raw observations (travel-time reliability, delay spread; p50/p95 planning-time pair)<TuxChartHistogram :percentiles="[50, 95]">
Vertical event timeline (project milestones, ingest/agent feed, session history)<TuxActivityTimeline>
Editorial wrapper for a multi-exhibit visualizations page (eyebrow + Oswald title + maroon signature + body + source)<TuxChartFrame>

If your need isn't here, scan /components (or app/utils/tuxCatalog.ts, the census the catalog test enforces) — there are ~140 Tux* components and this map only highlights the ones with the easiest-to-miss names.

Conventions

Prop & emit vocabulary — the words and what they mean

Ratified 2026-08-19 after a full-catalog vocabulary audit. TuxBadge and TuxSkeleton are the reference implementations. New components MUST use these words with these meanings; deviations listed below are grandfathered until the next major.

WordMeansValue shapeReference
toneSemantic colorinfo · success · warning · error · neutral (semantic family) — or brand-paint (maroon · gold · charcoal) on marketing surfacesTuxBadge, TuxActivityTimeline
kindStructural presetnamed layout/content presets (faq, first-run, tag)TuxBadge, TuxSkeleton, TuxEmptyState
variantEditorial styledefault · bold · elegant (the section-style triad) — or a visual style set where the triad doesn't applyTuxBigStat, TuxCallout, TuxSkeleton
intentAction semantics on interactive controlsprimary · secondary · ghost · destructiveTuxButton
sizeScalet-shirt (sm · md · lg) for components; px number for chart canvases; paper (letter · a4) for report framesTuxAvatar, charts
densityRow/space compactnesscomfortable · compactTuxDataTable, TuxRichDataGrid
layoutArrangementprefer inline · stacked (+ grid · columns where real)TuxDescriptionList
emit hoverChart roving-cursor payload (payload | null)all interactive charts
emit selectUser picked an itemTuxAlphaNav, TuxTree
emit update:xv-model bridge onlyeverywhere

Two meanings of tone, on purpose: product surfaces use the semantic family (status colors); marketing/editorial surfaces (TuxCTA, TuxCardSlab, TuxPhotoGrid, …) use brand-paint values. Both are "which color is this thing" — that's the invariant. Brand-paint values name the actual paint (maroon, gold, charcoal, navy, neutral) — never alias one paint word for a different color.

The v2.0.0 rename batch LANDED 2026-08-19 (see CHANGELOG "Breaking"): TuxFactoid.columns, TuxBetaRibbon.kind, TuxTabs.variant, TuxChartGauge band tone: success·warning·error, TuxCodeMaroon.tone (error replaces alert), TuxTestimonial.tone, TuxConfirmDialog drops the danger alias, select replaces pick (TuxConversationList/TuxSuggestionChips) and citation open (TuxCitations/TuxInlineCitation), stacked replaces stack (TuxStatComparison/TuxMapLegend), TuxFigureCaption/TuxTableCaption label replaces free-form kind, TuxRichTextEditor's duplicate bare update emit removed, and TuxTreemap joins the family hover emit.

Deviations kept, by decision: TuxResultCount emits update:page (a legitimate v-model:page) while TuxPagination uses update:modelValue — both are idiomatic Vue; TuxCommentThread's comment:*/thread:* colon namespace; TuxCommandPalette.open meaning "panel opened".

Composition standards for cases where a slot or prop combination shows up across multiple Tux* components or consuming apps. Capturing them here keeps consumers from inventing different icons or labels for the same affordance.

Chat-message actions

TuxChatMessage exposes a #tools slot for the row of small actions that sit below an assistant response (copy, regenerate, like, etc.). The Vercel AI Elements Actions reference shows the pattern; TUX standardizes on this five-icon set so consumers don't pick five different copy icons:

ActionIconLabelEmit (host-wired)Notes
Copylucide:copy"Copy"copyCopies the message body to clipboard
Regeneratelucide:refresh-cw"Regenerate"regenerateRe-runs the prompt that produced this response
Sharelucide:share-2"Share"shareOpens host-chosen share affordance
Helpfullucide:thumbs-up"Helpful"feedback w/ 'up'Positive feedback signal
Offlucide:thumbs-down"Off"feedback w/ 'down'Negative feedback signal

Order convention: Copy · Regenerate · Share · Helpful · Off when all five appear. Use a subset if the surface doesn't support an action (e.g., no regenerate when the response is already final). tti-ai-studio-session.vue is the canonical example using a Copy / Helpful / Off subset.

Style: icon + label (label can hide at narrow widths), font-size text-xs, color text-text-muted, hover transitions to text-brand-primary. Sits in a flex row below a border-t rule.

Form validation — when to use which

Backstage's design system dedicates 38 frames to form validation (inline errors, dialogs, banners) because where validation lives matters as much as what it says. TUX standardizes on four placements; the right one depends on scope (field / form / page / session) and severity (advisory / blocking).

PlacementUse whenComponent(s)Behavior
Inline field errorA single field is invalid. Most common case — wrong format, out of range, required-but-emptyUFormField with error prop, or compose <TuxInfoLabel> + <UInput> + an error <p class="text-text-danger text-xs"> belowRenders red text below the field; non-blocking; clears on next valid input
Inline form summaryMultiple fields invalid AND the user has tried to submit. Tells them how many issues without scrolling<TuxAlert variant="danger"> above the form body, with a list of <a href="#field-id"> jumps to each broken fieldPersists until all issues resolved; field-level inline errors remain the source of truth
Blocking dialogThe action being submitted is destructive or irreversible. Forces explicit confirmation<UModal> (or <TuxModal> for editorial chrome) with primary/secondary actionsBlocks the page; "Delete this corpus?" / "Discard 12 unsaved changes?" / "Revoke API key — this can't be undone"
Page/session bannerThe issue is not about this form — it's a session-level constraint that affects what the form can do (ITAR scope, expired token, rate limit, server down)<TuxAlert variant="compliance" | "danger" | "warning"> at the top of the pagePersists across forms on the same page; doesn't compete with field-level errors
Toast (transient confirmation)The form succeeded and the user has moved on. Not a "validation" placement per se, but the closing half of the validation lifecycleuseTuxToast().success("Saved") rendered by the app-shell <TuxStatusToast> hostAuto-dismisses in ~5 seconds (errors stick); non-blocking; escalates to OS notification in unfocused Tauri windows

Decision tree:

  1. Single field is wrong → inline field error
  2. Multiple fields wrong + user tried to submit → inline form summary + keep inline errors
  3. Destructive action being submitted → blocking dialog
  4. Session-level constraint (ITAR, auth, rate limit) → page/session banner
  5. Form succeeded → toast

Anti-patterns to avoid:

  • Modal dialog for a single bad field — friction without value; the inline error is faster
  • Banner for a single bad field — wrong scope; the user has to find which field
  • Inline error for a session-level issue — the user fixes the field but can't progress; root cause lives above
  • Toast for an error — transient and easily missed; use banner or dialog for anything the user needs to act on

Canonical references for each pattern in real composition: forms/inline-validation.vue (field-level), forms/all-in-one.vue (form summary), landscape-dashboard ITAR alert (session banner).

First-run AI surfaces — Examples / Capabilities / Limitations

When a consumer surface needs richer first-run framing than the single- block TuxEmptyState kind="first-run" (e.g. tti-ai-studio's new-session splash), use ChatGPT's canonical three-column taxonomy. Each column sets a different expectation before the user types their first prompt:

ColumnWhat it answersTypical content
Examples"What should I ask?"3 concrete prompt strings the user can click to populate the composer
Capabilities"What can it do?"3 strengths — corpora it can ground against, formats it can write, languages it understands
Limitations"What should I not trust?"3 advisory caveats — knowledge cutoff, hallucination risk, ITAR scope boundaries

Compose with <TuxFactoid density="3"> (numerical-statistic style) or a 3-up <TuxCard> grid (richer per-cell layout). The taxonomy is the discipline; the components rendering it are the consumer's choice. No dedicated TuxFirstRunTaxonomy component is needed — the value is the framing, not the chrome.

Anti-patterns to avoid:

  • Single-paragraph welcome. "Welcome to tti-ai-studio." reads as marketing, not orientation. The three columns set expectations before the user invests their first prompt.
  • Marketing tone in research IT. ChatGPT's first-run is a consumer surface ("Hi, what's on your mind today?"). Research-IT first-run should be quieter and more functional — "Recent session was about X · Pick up where you left off." Keep the taxonomy; drop the marketing voice.
  • More than three columns. The discipline is exactly three — the gain over a flat "welcome" is the parallelism. Four blurs it.

Reference frame: reference/figma-cache/chatgpt-ui-kit-ai-chat/screens/new-chat.png. Absorbed 2026-05-21 from the ChatGPT UI Kit Figma file.

When an MCP (Model Context Protocol) tool inside tti-ai-studio returns output to render in the chat, three display tiers cover the spectrum. Each is the canonical TUX composition; pick by the density and interactivity of the result.

TierUse whenTUX composition
Inline cardSingle result fits a card.Static result (place, chart, document, record): <TuxArtifact> — title + icon + meta header, copy/download/regenerate/share actions. Interactive third-party app surface: <TuxMcpEmbed> — app icon + name + window controls (collapse/expand/exit), built-in skeleton, container slot for the app's UI.
Inline carousel3+ comparable results — list of places, sources, datasets, results. The user scans across, sometimes clicks one.<TuxCardCarousel> with arrows + dots, sized to ~280–360px cards. Each card is a <TuxArtifact> (static) or <TuxMcpEmbed> (interactive). Header eyebrow names the tool (e.g. nearby trails) + count.
Full screenRich interaction needed — large data viz, multi-step picker, app-like surface that competes with chat scroll.Listen for <TuxMcpEmbed>'s @expand event and switch to a focus-view layout (<TuxFocusView>, <TuxSlideover> side-docked, or <TuxModal> centered) holding the same embed. Static surfaces compose <TuxArtifact> inside the same shells.

Decision tree:

  1. One static result + read-only → inline card (TuxArtifact).
  2. One interactive app surface → inline card (TuxMcpEmbed).
  3. Multiple comparable results + scanning → inline carousel.
  4. Heavy data / app needs >½ chat width → full screen.

Each tier has a skeleton state for the loading window — TuxMcpEmbed ships its own (three shimmer bars + a content block, honors prefers-reduced-motion); Nuxt UI 4's UChatShimmer covers TuxArtifact inline; TuxSkeleton covers full-screen loads.

Anti-patterns to avoid:

  • Full-screen for a one-line answer. The takeover surface is expensive; if the response fits a sentence, just render it in the chat body.
  • Inline carousel for two items. Use two side-by-side <TuxArtifact> blocks (or a single card with a comparison callout) — the carousel chrome (arrows, dots, advance buttons) overweights two items.
  • Inline card when the result is a list. A 12-result tool dump inside one card is unreadable; promote to inline carousel.

Reference frame: reference/figma-cache/mcp-apps-for-claude/frames/. Absorbed 2026-05-21 from Anthropic's official MCP Apps UI kit v1.1.

Faceted vs relational filtering — when to use which

Two filter components, two shapes of question. Reach for the one that matches what the user is actually doing — they're sisters, not substitutes.

Shape of questionComponentExample
Narrow a list along known axes. Buckets are pre-aggregated; the user multi-selects from each.<TuxFilterPanel> (faceted)"Owner ∈ {Chen, Kim} AND type ∈ {PDF, CSV} AND district ∈ {Bryan, Austin}" — a Landscape browse / results page.
Express a relational predicate across heterogeneous fields. Operators matter; numeric / date comparisons appear; nesting appears.<TuxRuleBuilder> (relational)"compliance < 50% AND last inspected before 2024-01-01 AND treatment IS rumble" — a research-dashboard "save as smart view" flow.
Both, on the same surface. Facets do the dominant narrowing in the left rail; rule builder lives in a slideover for the long-tail query.<TuxFilterPanel> + <TuxRuleBuilder>Operational data surface where some users browse and some authors save smart views.

Anti-patterns:

  • TuxFilterPanel for relational predicates. If you find yourself generating a facet bucket per >50% / >60% / >70% threshold, the user wanted a numeric comparator. Switch to TuxRuleBuilder.
  • TuxRuleBuilder for "owner = X." If the dominant axis is multi-select narrowing across a known set, the rule builder makes the user click 3× for what a single facet checkbox covers. Use facets and keep the rule builder for the predicates that don't fit.
  • Nesting beyond 3 levels. TuxRuleBuilder caps depth at 3 by default. Deeper trees signal the query should be split into named saved views the user composes, not a single deeply-nested predicate.

Empty states — no decorative illustrations

TuxEmptyState deliberately avoids the saturated-cartoon illustrations common in consumer-SaaS empty-state kits. TUX is research-publishing; editorial restraint outweighs the "consumer SaaS expectation" of a friendly mascot. Empty states use a Lucide icon + heading + body + optional CTA. If warmth is needed, place a real photograph (a future TuxPhotoCard — roadmap; today a TuxCaptionedMedia) next to — not inside — the empty state.

This stance was reaffirmed against the Empty State Illustration Kit absorption (2026-05-21). The five kind presets (no-data / no-results / not-found / no-permissions / first-run) cover the real scenario taxonomy; 200 generic illustrations would not.

Focus rings — the universal rule owns them

The layer ships ONE focus affordance: *:focus-visible in globals.css applies the themed two-ring --shadow-focus (--focus-ring-inner / --focus-ring-outer, remapped per theme — black-on-white in high-contrast). Components must NOT restate their own ring: a per-component outline: 2px solid <color> double-draws on top of the shadow, and a per-component outline: none silently suppresses the universal ring (2026-08-12 audit found four competing recipes across ten components — all removed).

Exactly two sanctioned exceptions:

  1. SVG child elements (<rect>, <path>, …) — box-shadow cannot render on them, so a focusable SVG child carries its own outline: 2px solid var(--focus-ring-outer) (the themed token, never a brand color). TuxTreemap's cells are the canonical case. Plot-level capture rects may instead use the documented wash-fill alternative (TuxChartLine's hover capture).
  2. @media (forced-colors: active) blocks — Windows High Contrast rings use system colors by design; leave them.

Motion primitives — six canonical transitions

Absorbed from the Tailwind Headless UI w/ Animations Figma kit and reaffirmed by the chart-family entrance animations shipped 2026-05-22. Every Tux component that animates uses one of these six patterns. Designers and contributors should reach for the existing primitive, not invent new motion.

PrimitivePatternDurationWhen to use
Tooltip / Popoverfade + scale 0.95 → 1fast (~150ms)TuxTooltip, TuxInfoLabel, TuxInlineCitation, chart tooltips
Dropdown menuslide-down (-8px → 0) + fadefast (~150ms)UDropdownMenu, TuxMenuBar triggers
Modal / Dialogfade + scale 0.95 → 1 (or sheet slide on Mac)normal (~200ms)TuxModal, TuxFocusView
Slideover / Drawerslide-from-edgenormal (~250ms)TuxSlideover, sidebar mobile-mode
Toast / Notificationslide-from-edge + fadenormal (~200ms)TuxStatusToast + useTuxToast(), Tauri OS-notification escalation
Accordion / Disclosureheight auto + fadefast (~150ms)TuxAccordion, UCollapsible

Plus three component-level entrance primitives shipped 2026-05-22 in tux-component-motion.css:

  • Mount fade-up (4px slide + fade, 220ms) — opt-in via .tux-mount-in on a section; immediate children animate in with optional stagger via --tux-mount-stagger-index.
  • Mount fade (no slide, 180ms) — for chrome surfaces like alerts and badges that shouldn't move.
  • Mount scale (96% → 100%, 200ms) — for popovers / floating chrome that originate from a trigger.

Plus six chart-family entrance primitives in tux-chart-motion.css:

  • Lines draw on via animated stroke-dashoffset.
  • Bars scale-Y from baseline (or scale-X horizontally).
  • Areas + top-lines rise from scale-Y 0.94.
  • Donut slices spin in -12° → 0, staggered.
  • Scatter dots pop from scale(0), staggered.
  • Gauge fill sweeps via stroke-dashoffset; needle swings.

All motion primitives collapse to instant on @media (prefers-reduced-motion: reduce). This is non-negotiable — no contributor escapes it, no "delight" affordance ignores it.

Chart tooltips — consistent pattern across the family

Every interactive chart in the native chart family uses the same tooltip pattern, shipped 2026-05-22 (TuxChartLine, TuxChartBar, TuxChartArea, TuxChartScatter) and extended to TuxChartDonut, TuxChartSunburst, and TuxTreemap 2026-08-12:

  1. Hover via pointer or focus via keyboard — one tab stop per chart. Lines / Areas / Bars accept a focusable rect across the plot area; Scatter / Donut / Sunburst make the <svg> itself the single focusable surface. Arrow keys cycle the active item as a roving cursor (scatter points in x order; donut slices clockwise; sunburst segments in hierarchical order — group, then its children). Never make every data point a tab stop: a 500-point scatter must not inject 500 tab stops. Heatmap keeps the single tab stop but its roving cursor is two-dimensional — Left/Right walk columns, Up/Down walk rows — because collapsing a matrix to a 1-D walk would alias the axes. Treemap is the one exception — its cells are real drill-in buttons (role="button", Enter/Space drills, Backspace drills up, arrows walk cells), so per-cell focus is the correct semantic.
  2. Visual highlight — vertical guide line + per-series focus dots (Line / Area); column-wash on the active category (Bar); active-dot grow +2px (Scatter).
  3. Branded tooltip card with the active label, per-series values, and (where applicable) totals, comparison overlays, or previous-period readouts.
  4. Auto-flip — when the active point sits past 60% of chart width, the tooltip flips to the left of the cursor so it doesn't overflow the parent container on the right.
  5. hover emit — every chart emits hover with the active payload so consumers can sync hover state across multiple panels (e.g., highlight the matching row in an adjacent table).
  6. tooltip prop — pass :tooltip="false" to disable when the chart is used in a print / PDF / non-interactive context.
  7. Native SVG <title> fallback remains on all hover-capable elements for AT users who don't see the branded card.

When building a new chart that needs hover behavior, follow this pattern rather than inventing a parallel one. The visual + a11y + emit contract is the canonical TUX chart-tooltip shape.

Ideas not yet shipped

The catalog originally listed several components here. As of the current cycle, every aspirational entry has shipped. New components land when a consuming app (Landscape, tti-ai-studio, marcom WordPress kit) needs them; open an issue or ping the maintainer to add a row.

Guidance for adding a new component

  1. Write it against Nuxt UI where possible — inherit a11y, theming, color-mode integration for free. Only go native when a Nuxt UI primitive fights TTI branding too hard to override cleanly (see TuxCard for an example of when that happens).
  2. Use container queries, not viewport media queries, for any layout that shifts based on width. Tux components get embedded at varying widths — full-page, sidebar tile, narrow article column, demo wrapper, modal body. Viewport queries firing on a 1200px viewport while the component lives in a 600px column is the single most common bug we hit.
    The pattern:
    .tux-component {
      container-type: inline-size;
      container-name: tux-component;
      /* base styles, mobile-first */
    }
    
    @container tux-component (min-width: 44rem) {
      .tux-component__layout {
        grid-template-columns: 1.4fr 1fr;
      }
    }
    

    Use cqi units in clamp() for fluid typography that scales with the container, e.g. font-size: clamp(2rem, 1.2rem + 4.5cqi, 3.5rem).
    Viewport queries are still right when the component does something inherently page-layout-relative — breaking out of an article column with negative margins, or floating into the page gutter. TuxCaptionedMedia's align="wide" and align="right" are the canonical examples; both keep @media queries with an explanatory comment.
  3. Hover/floating UI inside a bounded canvas (tooltips, popovers, dropdowns) should flip placement near canvas edges — anchor with right/bottom past the midpoint instead of left/top. See TuxTreemap's tooltip handler for the canonical pattern.
  4. Add a showcase route at /components/<kebab-name>. Use TuxExample with at least a vue prop so the Vue template is exposed. If the component is load-bearing for the brand, also pass source so readers can see the SFC.
  5. Dogfood TuxPageHeader at the top of the page — keeps visual rhythm consistent across every component demo.
  6. Add the entry to app/app.vue nav, app/pages/index.vue Foundations/Components grid, and app/pages/components/index.vue table.
  7. Update CHANGELOG.md under Unreleased → Added.
  8. Run the local guards before you push — don't wait for CI to find what you could catch in seconds while the component is still fresh in your head:
    • npm run lint + npm run typecheck — style + types.
    • npm test — catalog conformance plus the mounted component suites (tests/components/*.nuxt.test.ts, nuxt environment via // @vitest-environment nuxt docblock, mounted with mountSuspended). New interactive behavior — keyboard contracts, persistence, emits — should land with a mounted test; the chart tooltip suites are the house exemplars.
    • npm run audit:tokens — every var(--token) you wrote resolves to a defined token (or fallback / known external namespace). Catches the --surface-base class of bug (undefined token → renders transparent), invisible to typecheck and the contrast audit.
    • npm run generate && npm run audit:a11y — structural axe-core pass (roles, landmarks, accessible names, nesting) over your new showcase page. npm run audit:contrast covers color separately. All three are blocking in CI, so a clean local run is a clean PR.

A component that skips any of these is invisible to future readers — the style guide is its own documentation, so being in the guide is the definition of "shipped."