PVR Tech Studio
Core and feedback

Core & Feedback

The everyday presentational building blocks — buttons, cards, badges, stat tiles, avatars, alerts, progress, toasts and navigation aids, every one token-driven and reduced-motion safe.

20 min read
Updated July 15, 2026

Overview

These are the reusable presentational primitives that the rest of the template composes with. They live in src/components/ui/ and are re-exported from a single barrel (src/components/ui/index.ts), so you import everything from one place:

import {Button, Card, CardBody, Badge, StatCard, Alert, useToast} from '@/components/ui'

Every component follows the same house rules:

  • Colors come only from semantic tokens (bg-surface, text-foreground, border-border, bg-primary, success/warning/danger/info …) — never hardcoded hex — so all 13 skins and dark mode work automatically. See Design tokens & dark mode.
  • Motion is token-driven (src/lib/motion.ts) and reduced-motion safe — animations tone down or disable when the user prefers reduced motion. See Animation & effects.
  • Named exports, interface XxxProps for props, and cn() (src/lib/cn.ts) for class composition.

For overlays and disclosure primitives (Modal, Popover, Dropdown, Tabs, Accordion) see Overlays; for the collapsible content Panel see Panel; for form controls see Forms.

Architecture & files

FileExports
src/components/ui/Button.tsxButton, ButtonSizeProvider
src/components/ui/Card.tsxCard, CardHeader, CardBody
src/components/ui/Badge.tsxBadge
src/components/ui/StatCard.tsxStatCard
src/components/ui/SectionHeading.tsxSectionHeading
src/components/ui/Switch.tsxSwitch
src/components/ui/Tooltip.tsxTooltip
src/components/ui/Alert.tsxAlert
src/components/ui/Progress.tsxProgress, ProgressRing
src/components/ui/Avatar.tsxAvatar, AvatarGroup
src/components/ui/Skeleton.tsxSkeleton
src/components/ui/Carousel.tsxCarousel
src/components/ui/Lightbox.tsxLightbox
src/components/ui/CodeBlock.tsxCodeBlock
src/components/ui/Pagination.tsxPagination
src/components/ui/Breadcrumbs.tsxBreadcrumbs
src/components/ui/BackToTop.tsxBackToTop
src/components/ui/RouteProgress.tsxRouteProgress
src/components/ui/OptionPills.tsxOptionPills
src/components/ui/Toast/ToastProvider.tsxToastProvider, useToast
src/components/ui/Toast/Toaster.tsxToaster
src/components/ui/index.tsBarrel — re-exports all of the above

The barrel also re-exports the form/input primitives (Input, Select, Checkbox, DatePicker, Combobox, Slider, Rating, Stepper, …) — those are documented in Forms. The overlay/disclosure set (Modal, Popover, Dropdown, Tabs, Accordion) is in Overlays and Panel in Panel. Note: RichTextEditor is deliberately not in the barrel (it would hoist Tiptap into the main bundle) — import it directly from @/components/ui/RichTextEditor in lazy pages.

Live demos of each component are in src/pages/ui/ (ButtonsPage, BadgesPage, AlertsPage, ProgressPage, CarouselPage, LightboxPage, PaginationPage, NotificationsPage, TypographyPage, plus IconsPage and WidgetsPage); SectionHeading is demoed across the src/pages/forms/* pages and CodeBlock on the email-template browser.

Usage

Import from the barrel and drop the component in. Most are pure presentational components with no provider requirement — the one exception is the toast system, which needs <ToastProvider> in the tree and a single root-mounted <Toaster/> (already wired in src/main.tsx).

import {Card, CardHeader, CardBody, Button, Badge} from '@/components/ui'
 
function Example() {
    return (
        <Card>
            <CardHeader title="Team plan" subtitle="Renews monthly" action={<Badge tone="success">Active</Badge>} />
            <CardBody>
                <Button>Manage subscription</Button>
            </CardBody>
        </Card>
    )
}

API / Props

Buttons & actions

Button / ButtonSizeProvider

Primary interactive control. Renders a motion.button with a spring hover/tap scale, so it accepts all HTMLMotionProps<'button'> (onClick, disabled, type, aria-*, and motion props) plus the props below.

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'outline' | 'ghost' | 'soft' | 'gradient' | 'danger' | 'solid''primary'Visual style. solid/soft are tone-aware (see tone); the rest are fixed-color.
tone'primary' | 'success' | 'warning' | 'danger' | 'info''primary'Status color for the solid / soft variants (ignored by structural variants).
size'xs' | 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon' | 'icon-lg'resolved: size ?? context ?? 'md'Control height/padding. icon* sizes are square (h-8/h-10/h-11).
pillbooleanfalseFully rounded (capsule) shape.
loadingbooleanfalseShows a size-matched spinner and disables the button; children stay rendered so width holds.
classNamestringExtra classes, merged via cn().
…restHTMLMotionProps<'button'>Native button + motion props.

Status colors are centralized in the component — the tone × variant class maps (solidTone / softTone in Button.tsx) are the single home for status-button colors, so pages never rebuild them in className recipes. variant="primary" and variant="danger" are shorthands for variant="solid" with the matching tone. Solid non-primary tones darken on hover via hover:brightness-90 (there are no --<tone>-hover tokens) so a hover never falls back to a surface grey; text uses the *-foreground tokens (never a hardcoded text-white), which keeps contrast correct on skins like Amber.

ButtonSizeProvider sets a default size for every Button inside it without passing size on each:

PropTypeDefaultDescription
size'xs' | 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon' | 'icon-lg'Default size applied to descendant buttons.
childrenReactNodeContent.

Size resolution is explicit prop ?? context ?? 'md'. PageHeader wraps its action slot in <ButtonSizeProvider size="sm">, so page-header action buttons should omit size (they become sm automatically); only set size to override.

OptionPills

A labelled group of single-select "pill" buttons — the shared control behind the Layout Customizer and Layout Settings pickers (toast position, page transition, page loader, splash frequency). Option labels resolve in the customizer i18n namespace.

PropTypeDefaultDescription
labelstringSection label above the pills.
optionsreadonly {id: T; key: string}[]Options; key resolves via t() in the customizer namespace.
valueTCurrently selected id.
onChange(id: T) => voidSelection handler.
classNamestringOuter wrapper classes.
groupClassNamestringPill-container layout classes (e.g. grid grid-cols-2).
pillClassNamestringExtra per-pill classes (e.g. padding density).

Surfaces

Card / CardHeader / CardBody

The standard rounded, bordered surface (rounded-xl border border-border bg-surface shadow-sm). Card extends HTMLAttributes<HTMLDivElement>; CardBody is a padded div.

Card

PropTypeDefaultDescription
hoverbooleanfalseSubtle shadow raise on hover (no layout shift).
…restHTMLAttributes<HTMLDivElement>Native div attributes.

CardHeader (extends Omit<HTMLAttributes<HTMLDivElement>, 'title'>)

PropTypeDefaultDescription
titleReactNodeHeader title (rendered as h3).
subtitleReactNodeSecondary line under the title.
actionReactNodeRight-aligned slot (button, badge, menu).
childrenReactNodeExtra content under title/subtitle.

CardBody takes plain HTMLAttributes<HTMLDivElement> (padded p-5).

Switch

Accessible toggle. The whole row is a single role="switch" button — never nest a <button> in a <label htmlFor> (double-fires). The thumb animates via Motion layout (flips justify-content).

PropTypeDefaultDescription
checkedbooleanControlled on/off state.
onChange(checked: boolean) => voidToggle handler.
labelstringRow label.
descriptionstringSecondary line under the label.
idstringId for aria/label association.
iconReactNodeOptional leading icon (inside the same button).

Data display

Badge

Small rounded status pill with a subtle hover scale. Extends ComponentPropsWithoutRef<typeof motion.span> (accepts className, children, motion props).

PropTypeDefaultDescription
tone'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info''neutral'Color tone.
variant'soft' | 'solid' | 'outline' | 'dot''soft'Tinted soft, high-contrast solid, border-only outline, or a surface chip with a status dot.
size'sm' | 'md' | 'lg''md'Pill padding/text size.
onRemove() => voidRenders a trailing × button (i18n common:remove label) — makes the badge a removable chip.

With variant="dot" the badge sits on a bordered surface and the tone colors only the leading dot.

StatCard

Dashboard KPI tile with a count-up value (AnimatedNumber), a pointer-tilt (via the shared TiltCard wrapper from src/components/motion, max={6}), and an optional delta indicator. It renders as a staggerItem, so it drops straight into a Stagger grid. All motion is reduced-motion aware.

PropTypeDefaultDescription
labelstringMetric label.
valuenumberNumeric value (animated count-up).
iconLucideIconIcon shown in the tinted corner badge.
prefixstringText before the value (e.g. $).
suffixstringText after the value (e.g. k).
decimalsnumberDecimal places for the animated number.
deltanumberPercentage change; renders an up/down arrow (success/danger).
hintstringMuted caption next to the delta.

SectionHeading

Section divider heading — an icon chip + title with a muted hint — used to group panels within a page's Stagger grid (see the src/pages/forms/* pages). It renders a StaggerItem, so it drops straight into the grid; token-only, so it re-skins and dark-modes automatically.

PropTypeDefaultDescription
titleReactNodeHeading text (rendered as an h2 in the skin display font).
hintReactNodeMuted description under the title.
iconReactNodeOptional leading icon, shown in a primary-tinted chip.
classNamestring'lg:col-span-2'Wrapper class — defaults to spanning both columns of a 2-col grid; override for other grids (e.g. lg:col-span-3).

Typography (/ui/typography)

The Typography showcase page (src/pages/ui/TypographyPage.tsx) demonstrates the type system — the heading scale, article/body text, the type-scale reference table, and the fonts each skin uses. It is a showcase, not a reusable component; the reusable parts are the skin-aware font utilities defined in src/styles/index.css:

UtilityMaps toUse for
.font-displayvar(--font-display)Headings / display text. Each skin can override the display face (e.g. Console → JetBrains Mono, Bento keeps the default).
.font-datavar(--font-mono) + tabular-nums + tight trackingNumeric/data readouts where digits should align in columns.
.font-numeralJetBrains Mono (fixed, skin-independent)Oversized numerals (e.g. the error-page status codes).

The base UI font is Plus Jakarta Sans (--font-sans); ja/zh swap in Noto Sans JP / SC via html[lang] overrides. All font vars are tokens, so type re-skins with the active design. Never hardcode a font family in a component — use these utilities or the --font-* tokens. Full write-up: Typography (type scale, the showcase-page sections, per-skin & CJK overrides). See also Design tokens & dark mode for the token definitions and Design skins for the per-skin font overrides.

Avatar / AvatarGroup

Image avatar with an initials fallback (on missing/broken src) and an optional presence dot. AvatarGroup overlaps children and collapses overflow into a +N chip.

Avatar

PropTypeDefaultDescription
srcstringImage URL. Falls back to initials if absent/broken.
namestringUsed for the alt text and initials.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Avatar size.
status'online' | 'away' | 'busy' | 'offline'Presence dot color.
classNamestringExtra classes.

AvatarGroup

PropTypeDefaultDescription
maxnumber4Max avatars before the +N chip.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Size for the overflow chip.
childrenReactNodeAvatar elements.
classNamestringExtra classes.

Skeleton

Loading placeholder with a shimmer sweep (.skeleton in _skeleton.scss, reduced-motion safe).

PropTypeDefaultDescription
variant'text' | 'circle' | 'rect''rect'Shape preset. text is a rounded line; circle is round.
classNamestringSizing/extra classes.

Sliding carousel with arrows, dots, optional autoplay, fade mode, controlled index and drag-to-swipe. Slides animate in the swipe direction; reduced motion swaps all movement for a plain cross-fade and disables autoplay/drag.

PropTypeDefaultDescription
slidesReactNode[]One node per slide.
mode'slide' | 'fade''slide'Directional push, or cross-fade in place.
autoPlaynumberAuto-advance every N ms; pauses on hover, disabled under reduced motion.
showArrowsbooleantruePrev/next arrow buttons.
showDotsbooleantrueDot indicators (active dot stretches; aria-current).
aspectstring'aspect-[16/7]'Tailwind aspect class for the stage.
indexnumberControlled index — pair with onIndexChange (e.g. a thumbnail strip).
onIndexChange(index: number) => voidFires on every slide change.
classNamestringExtra classes on the frame.

Thumbnail grid that opens a full-screen portal viewer with prev/next, a position counter, and keyboard nav (←/→, Esc).

PropTypeDefaultDescription
imagesLightboxImage[]Array of {src, alt?}.
variant'grid' | 'masonry' | 'hero''grid'Trigger layout: uniform grid, span-mixed masonry, or a single 16:9 cover with a "+N photos" badge.
columns2 | 3 | 44Desktop grid columns (grid/masonry variants).
captionsbooleanfalseShow each image's alt as a caption bar in the viewer.
classNamestringExtra classes on the thumbnail grid.

LightboxImage: { src: string; alt?: string }.

CodeBlock

Editor-style read-only code viewer: a chrome bar (filename "tab" + language Badge + wrap/copy actions with Tooltips), a line-number gutter, and token-based syntax highlighting via src/lib/highlightHtml.ts — tokens render as plain text nodes in styled spans (no innerHTML). Syntax colors are semantic tokens only, so every theme/skin restyles the code automatically. Used by the email-template browser (src/components/email/EmailSourceModal.tsx).

PropTypeDefaultDescription
codestringThe source text.
filenamestringShown as the editor "tab", e.g. welcome.html.
languagestringBadge label; html is currently the only highlighted language (others render plain).
defaultWrapbooleanfalseStart with soft-wrapped lines instead of horizontal scroll.
wrapLabelstringt('common:wrapLines')Tooltip label for the wrap toggle.
copyLabelstringt('common:copyCode')Tooltip label for the copy button.
classNamestringExtra classes; pass h-full to fill a flex parent (the body scrolls internally).

Feedback

Alert

Inline, non-dismissable-by-default callout in four tones and four visual variants, with optional icon override, action slot and close button. Renders role="alert".

PropTypeDefaultDescription
tone'info' | 'success' | 'warning' | 'danger''info'Color + default leading icon.
variant'soft' | 'solid' | 'outline' | 'accent''soft'Tinted soft, tone-filled solid (on-color *-foreground text), border-only outline, or surface card with a left accent bar.
titleReactNodeBold title line.
childrenReactNodeBody content.
iconReactNodetone iconReplace the tone's default leading icon; pass null to hide it.
actionReactNodeAction slot rendered under the body — e.g. small Buttons.
onClose() => voidWhen set, shows a dismiss button (i18n common:dismiss) that calls this.
classNamestringExtra classes.

Progress / ProgressRing

Progress is an animated horizontal progress bar, clamped to 0–100; ProgressRing is its circular SVG sibling. Both expose role="progressbar" with aria-valuenow/min/max and share the same animation modes: default (spring-fill tracking the prop), animateOnView (fill once when scrolled into view — for below-the-fold showcases), and loop (a continuous 0→100 cycle whose value label counts along — one motion value drives both the fill and the label). All motion is reduced-motion gated (loop/stripes/indeterminate go static).

Progress

PropTypeDefaultDescription
valuenumberProgress 0–100 (clamped). Ignored while indeterminate.
tone'primary' | 'success' | 'warning' | 'danger' | 'info''primary'Bar color.
size'sm' | 'md' | 'lg''md'Track height; lg is tall enough to carry the value label inside the track.
showValuebooleanShow the numeric % — right of the track, or inside it when size="lg".
stripedbooleanAnimated diagonal stripes over the fill (paused under reduced motion).
indeterminatebooleanEndless sliding segment for unknown durations (static under reduced motion).
animateOnViewbooleanFill once when the bar scrolls into view instead of on mount.
loopbooleanContinuously cycle the fill 0→100 (a live "loading" loop); label counts along.
loopDelaynumber0Start offset (seconds) for loop — phase-shifts sibling bars out of lockstep.
classNamestringExtra classes on the wrapper.

ProgressRing

PropTypeDefaultDescription
valuenumberProgress 0–100 (clamped).
tone'primary' | 'success' | 'warning' | 'danger' | 'info''primary'Arc color.
size'sm' | 'md' | 'lg''md'Outer diameter: sm 48 · md 64 · lg 88 px.
showValuebooleanShow the % in the center.
animateOnViewbooleanAnimate the arc when it scrolls into view (once) instead of on mount.
loopbooleanContinuously cycle the arc 0→100; the center label counts along.
loopDelaynumber0Start offset (seconds) for loop.
classNamestringExtra classes.

Tooltip

Lightweight CSS-only tooltip — shows on hover and keyboard focus (uses :has(:focus-visible) so a mouse click doesn't pin it open). Wrap a trigger; the label floats to the chosen side.

PropTypeDefaultDescription
labelstringTooltip text.
childrenReactNodeThe trigger element to wrap.
side'right' | 'left' | 'top' | 'bottom''right'Which side the label floats to.
classNamestringExtra classes on the wrapper.

Toasts

ToastProvider / useToast / Toaster

Ephemeral notifications. ToastProvider holds the live toast list (capped at 5, newest wins), useToast fires them, and Toaster renders the stack via a portal. Collapsed cards form a 3D stack that spreads on hover; content is left-aligned with a tone icon.

Architecture note: <Toaster/> is mounted at the root in src/main.tsx (NOT in AppLayout) so toasts survive route changes — e.g. a "signed out" toast still shows after navigating to /auth/login. Its position is driven by config.toastPosition from the Layout Customizer / Layout Settings (an explicit position prop on <Toaster/> overrides). See Customizer & settings.

useToast() returns { toast, dismiss }. Fire with const {toast} = useToast():

ToastOptions (argument to toast(...))

FieldTypeDefaultDescription
titlestringRequired title.
descriptionstringOptional secondary line.
tone'neutral' | 'success' | 'warning' | 'danger' | 'info''neutral'Tone (icon + accent).
variant'default' | 'solid' | 'accent''default'Card style: surface card, tone-filled solid (on-color *-foreground text), or left accent-bar accent.
durationnumber4000Auto-dismiss after N ms; pass 0 to keep until dismissed.
action{label: string; onClick: () => void}Inline action button (e.g. Undo) — clicking it also dismisses the toast.
iconReactNodetone iconReplace the tone icon — e.g. an <Avatar/> for message-style toasts.
progressbooleanShow a countdown bar that drains over duration (hidden when sticky / reduced motion).

toast(opts) returns the numeric id; dismiss(id) removes it early.

Toaster props

PropTypeDefaultDescription
positionToastPositionconfig.toastPosition'center' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-right'. Overrides layout config when set.

The /ui/notifications showcase page

src/pages/ui/NotificationsPage.tsx (route /ui/notifications, sidebar Components → UI Elements → Notifications) is the live demo of the toast system — a gallery of buttons that fire example toasts so you can preview every tone, variant, and behavior. It's a showcase, not a component; the reusable API is useToast() documented above. Its sections:

SectionFiresDemonstrates
Tonestoast({tone}) for neutral / info / success / warning / dangerThe five tones (icon + accent).
With actiontoast({action: {label, onClick}})An inline action button (e.g. Undo) that also dismisses the toast.
Card stylestoast({variant}) for default / solid / accentThe three card treatments.

Position is not a per-page control here — every toast lands at config.toastPosition (top-center by default). Change it in the Customizer or Layout Settings, or use the Toast Position page-layout preview (/page-layouts/toast-position) to try positions live. See Page-Layout Previews.

Breadcrumb trail. Pass explicit items, or omit them to auto-derive the trail from the current route against the sidebar menu (single source of truth). Used by PageHeader.

PropTypeDefaultDescription
itemsCrumb[]auto-derivedExplicit crumbs; omit to derive from the route.
classNamestringExtra classes.

Crumb: { label: string; to?: string }.

Pagination

Page navigator with gaps around the current page. Returns null when there's a single page. Accessible markup: a <nav aria-label> wrapper, aria-current="page" on the active page button, translated common:prevPage/nextPage labels, and the minimal variant renders "Page X of Y" via <Trans i18nKey="common:pageOf"> so the bold page number survives translation.

PropTypeDefaultDescription
pagenumberCurrent page (1-based).
pageCountnumberTotal pages.
onPageChange(page: number) => voidCalled with the clamped target page.
siblingsnumber1Sibling pages shown either side of the current one.
variant'default' | 'pill' | 'outline' | 'minimal''default'Spaced buttons, rounded pills, a joined outline group, or minimal prev/next + "Page X of Y".
size'sm' | 'md''md'Control dimensions.
classNamestringExtra classes.

BackToTop

Floating "back to top" button that appears once the window scrolls past ~400px and smooth-scrolls to the top. No props — it reads window scroll via useScroll. Mounted once in AppLayout.

RouteProgress

Thin top loading bar that animates across on each route change (keyed on pathname so it replays). Returns null under reduced motion. No props — mounted once in AppLayout.

Configuration & customization

  • Restyle via tokens, not props. Colors/radii come from CSS custom properties in src/styles/index.css; changing a token re-skins every component. See Design tokens & dark mode.
  • Extend classes with className. Every component merges extra classes via cn() (clsx + tailwind-merge), so later utilities win over defaults.
  • Motion values live in src/lib/motion.ts (spring, springSnappy, staggerItem, EASE_OUT); reuse them instead of hardcoding numbers.
  • Toast position is a layout setting (config.toastPosition), not a per-call option.

Examples

Buttons with the size context (page-header pattern):

import {ButtonSizeProvider, Button} from '@/components/ui'
 
<ButtonSizeProvider size="sm">
    <Button variant="outline">Export</Button>
    <Button>New item</Button> {/* inherits sm */}
</ButtonSizeProvider>

Status buttons via the tone-aware variants (never rebuild these colors in className):

<Button variant="solid" tone="success">Approve</Button>
<Button variant="soft" tone="warning">Snooze</Button>
<Button variant="solid" tone="danger" loading={deleting} onClick={remove}>Delete</Button>

Looping progress trio (phase-shifted so they don't cycle in lockstep):

import {Progress, ProgressRing} from '@/components/ui'
 
<Progress value={72} showValue loop loopDelay={0} />
<Progress value={45} tone="success" showValue loop loopDelay={0.25} />
<ProgressRing value={88} tone="info" size="lg" showValue animateOnView />

A stat tile with delta and count-up:

import {StatCard} from '@/components/ui'
import {DollarSign} from 'lucide-react'
 
<StatCard label="Revenue" value={48250} prefix="$" delta={12.4} hint="vs. last month" icon={DollarSign} />

Firing a toast from anywhere in the tree:

import {useToast} from '@/components/ui'
 
function SaveButton() {
    const {toast} = useToast()
    return (
        <button onClick={() => toast({title: 'Saved', description: 'Your changes are live.', tone: 'success'})}>
            Save
        </button>
    )
}

Alert with dismiss + a progress bar:

import {Alert, Progress} from '@/components/ui'
 
<Alert tone="warning" title="Storage almost full" onClose={() => setHidden(true)}>
    You've used 92% of your plan.
</Alert>
<Progress value={92} tone="warning" showValue />

Avatar stack and a lightbox gallery:

import {Avatar, AvatarGroup, Lightbox} from '@/components/ui'
import {asset} from '@/lib/asset'
 
<AvatarGroup max={3}>
    <Avatar name="Jane Cooper" src={asset('/avatars/avatar-1.png')} status="online" />
    <Avatar name="Cody Fisher" src={asset('/avatars/avatar-2.png')} />
    <Avatar name="Esther Howard" />
</AvatarGroup>
 
<Lightbox images={[{src: '/img/a.jpg', alt: 'A'}, {src: '/img/b.jpg'}]} />

Best practices

  • Import from the barrel @/components/ui, not the individual files (exception: RichTextEditor, which is kept out of the barrel — import it directly in lazy pages).
  • Never hardcode a color — add or use a token so light/dark and all skins stay in sync.
  • Status-colored buttons go through variant="solid|soft" + tone — don't rebuild tone colors in per-page className recipes (the maps in Button.tsx are the single source).
  • Never nest interactive elements (a <button> inside a Switch row, or inside an Avatar button).
  • On page-header actions, omit size (the ButtonSizeProvider makes them sm).
  • Fire toasts through useToast(); keep messages short but let them wrap — never truncate them.
  • Mount <Toaster/>, <BackToTop/>, and <RouteProgress/> exactly once (already done in the shell).
  • Wrap user-facing strings in i18n t() — see Getting started.

Troubleshooting

SymptomLikely cause & fix
useToast must be used within <ToastProvider>The component isn't under ToastProvider. It wraps the app in src/main.tsx; ensure your tree is inside it.
Toasts don't appearNo <Toaster/> mounted, or it's mounted inside a route that unmounts. It belongs at the root in main.tsx.
Toast text is cut offSomething added truncate/line-clamp. Toast text must wrap (break-words) — remove the clamp.
Tooltip stays open after clickingExpected only with focus-within; this component uses :has(:focus-visible) to avoid it. If you see it, you're on a custom tooltip.
Avatar shows initials, not the imagesrc is missing or failed to load — the fallback is intentional. Check the path (/avatars/...).
Colors look wrong in dark mode / a skinA hardcoded hex slipped in. Replace with a semantic token.
Pagination renders nothingpageCount <= 1 returns null by design.

FAQ

How do I change where toasts appear? Set the toast position in the Layout Customizer or Layout Settings — it's driven by config.toastPosition. Only pass a position prop to <Toaster/> to force it.

Can Button be a link? It renders a motion.button. For navigation, wrap a router Link or use an onClick with navigate().

How do I show a spinner-style loading state? Use Skeleton placeholders shaped like the content (preferred over spinners in this template), or Progress for determinate progress.

Does StatCard require a chart library? No — the count-up is AnimatedNumber from src/components/motion, free-core Motion only.

Notes for designers & content editors

  • Tones map to meaning: success (green), warning (amber), danger (red), info (blue), primary (brand), neutral (muted). Use them consistently across badges, alerts, and toasts.
  • All copy is translatable. Labels, titles, and toast messages should be i18n keys, not literals (proper nouns and numbers stay literal).
  • Keep toast messages complete — they wrap and are never truncated, so write the full sentence.
  • Icons come from lucide-react; pick a matching icon for stat tiles and alerts.

Was this page helpful?