Component Library

Reference for the reusable, auto-imported Vue UI components shipped in the packages/ui layer.

Owlat includes a set of reusable Vue components in packages/ui/components/ui/.

When composing these components into a surface, follow the progressive-disclosure standard: lead with a verdict/summary, and keep deeper detail exactly one interaction away.

Overview

All components follow these principles:

  • Consistent styling - Uses design system tokens from main.css
  • TypeScript - Full type support
  • Accessible - ARIA attributes and keyboard navigation
  • Auto-imported - Available without explicit imports (Nuxt feature)

Button (UiButton)

Versatile button component with variants and states.

Props

PropTypeDefaultDescription
variantstring'primary''primary', 'secondary', 'outline', 'ghost', 'danger', 'danger-ghost', 'danger-outline'
sizestring'md''sm', 'md', 'lg'
loadingbooleanfalseShow spinner and disable
disabledbooleanfalseDisable button
fullWidthbooleanfalseFull width button

Slots

  • default - Button text
  • iconLeft - Icon before text
  • iconRight - Icon after text

Examples

<!-- Primary button -->
<UiButton variant="primary">Save</UiButton>

<!-- With loading state -->
<UiButton variant="primary" :loading="isSubmitting">
  Submit
</UiButton>

<!-- With icon -->
<UiButton variant="secondary">
  <template #iconLeft>
    <Icon name="lucide:plus" class="w-4 h-4" />
  </template>
  Add Item
</UiButton>

<!-- Danger button -->
<UiButton variant="danger">Delete</UiButton>

Input (UiInput)

Text input with label, error state, and icons.

Props

PropTypeDefaultDescription
typestring'text''text', 'email', 'password', 'number', 'date'
modelValuestring/number-v-model value
labelstring-Label text
placeholderstring-Placeholder text
errorstring-Error message
helpTextstring-Helper text (shown when no error)
requiredbooleanfalseShow required asterisk
disabledbooleanfalseDisable input
sizestring'md''sm', 'md'

Slots

  • iconLeft - Icon in left side of input
  • iconRight - Icon in right side of input

Examples

<!-- Basic input -->
<UiInput v-model="email" label="Email" type="email" placeholder="you@example.com" />

<!-- With error -->
<UiInput v-model="email" label="Email" :error="errors.email" required />

<!-- With search icon -->
<UiInput v-model="search" placeholder="Search...">
  <template #iconLeft>
    <Icon name="lucide:search" class="w-4 h-4" />
  </template>
</UiInput>

Select (UiSelect)

Custom dropdown built from a <button> trigger and a floating menu (not a native <select>). Opening shows a transition-animated list with a Lucide chevron on the trigger and a check icon next to the selected option; the menu dismisses on click-outside or Escape. The component is generic over T extends string | number.

Props

PropTypeDefaultDescription
optionsarray[{ value, label }]
modelValueT | nullnullv-model value (string or number)
labelstring-Label text
placeholderstring'Select an option'Text shown when no option is selected
errorstring-Error message
requiredbooleanfalseShow required asterisk
disabledbooleanfalseDisable select
sizestring'md''sm', 'md'

Examples

<UiSelect
    v-model="country"
    label="Country"
    placeholder="Select a country"
    :options="[
        { value: 'us', label: 'United States' },
        { value: 'uk', label: 'United Kingdom' },
        { value: 'de', label: 'Germany' },
    ]"
/>

Textarea (UiTextarea)

Multi-line text input with character count.

Props

PropTypeDefaultDescription
modelValuestring-v-model value
labelstring-Label text
placeholderstring-Placeholder text
rowsnumber4Number of rows
maxLengthnumber-Max characters (shows counter)
resizestring'none''none', 'vertical', 'both'
errorstring-Error message
requiredbooleanfalseShow required asterisk

Examples

<UiTextarea
    v-model="description"
    label="Description"
    :rows="4"
    :max-length="500"
    resize="vertical"
/>

Checkbox (UiCheckbox)

Checkbox with label and optional description.

Props

PropTypeDefaultDescription
modelValuebooleanfalsev-model value
labelstring-Label text
descriptionstring-Helper text below label
disabledbooleanfalseDisable checkbox

Examples

<UiCheckbox v-model="acceptTerms" label="I accept the terms and conditions" />

<UiCheckbox
    v-model="newsletter"
    label="Subscribe to newsletter"
    description="Receive weekly updates about new features"
/>

Switch (UiSwitch)

Track-and-thumb toggle with role="switch" semantics — the on/off control used across settings, preferences, and config cards. Use UiSwitch for a sliding track toggle; use UiToggle for the icon-style toggle, and keep a bespoke control for tri-state (on/partial/off) feature packs.

Props

PropTypeDefaultDescription
modelValuebooleanfalsev-model value
disabledbooleanfalseDisable the switch
labelstring-Accessible name — required when no visible label references the switch

Examples

<UiSwitch v-model="autoReplyEnabled" label="Enable auto-reply" />

<UiSwitch v-model="featureOn" :disabled="!canToggle" label="Knowledge graph" />

Progress Bar (UiProgressBar)

Determinate or indeterminate progress bar with role="progressbar". Used for imports, sending limits, and any unknown-duration work (indeterminate sweep). Honors prefers-reduced-motion.

Props

PropTypeDefaultDescription
valuenumber00–100. Ignored when indeterminate is true.
variantstring'brand''brand', 'success', 'warning', 'error'
indeterminatebooleanfalseAnimated sweep for unknown-duration work
sizestring'md''sm', 'md' (track height)
ariaLabelstring-Accessible name announced by screen readers

Examples

<UiProgressBar :value="importPercent" :aria-label="`Importing: ${importPercent}%`" />

<UiProgressBar indeterminate variant="brand" aria-label="Discovering messages" />

<UiProgressBar :value="95" variant="warning" size="sm" aria-label="Sending limit" />

Card (UiCard)

Container with variants and slots.

Props

PropTypeDefaultDescription
paddingstring'md''none', 'sm', 'md', 'lg'
variantstring'default''default', 'info', 'warning', 'error'
hoverablebooleanfalseAdd hover border effect
clickablebooleanfalseAdd cursor and emit click
overflowstring'visible''visible', 'hidden'

Slots

  • default - Main content
  • header - Header with bottom border
  • footer - Footer with top border

Examples

<!-- Simple card -->
<UiCard padding="md">
  Card content here
</UiCard>

<!-- Card with header and footer -->
<UiCard padding="none">
  <template #header>
    <div class="px-6 py-4">
      <h2>Card Title</h2>
    </div>
  </template>

  <div class="px-6 py-4">
    Content here
  </div>

  <template #footer>
    <div class="px-6 py-4 flex justify-end">
      <UiButton>Action</UiButton>
    </div>
  </template>
</UiCard>

<!-- Info callout -->
<UiCard variant="info" padding="md">
  <p>This is informational text.</p>
</UiCard>

Dialog overlay with backdrop.

Props

PropTypeDefaultDescription
openbooleanfalsev-model:open
titlestring-Modal title
sizestring'md''sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', 'full'
closablebooleantrueShow close button
persistentbooleanfalseDisable backdrop click close
zIndexnumber-Custom z-index to render above high-z elements like the email builder

Slots

  • default - Modal body
  • footer - Modal footer (buttons)

Events

  • update:open - For v-model

Examples

<template>
    <UiButton @click="showModal = true">Open Modal</UiButton>

    <UiModal v-model:open="showModal" title="Confirm Action" size="sm">
        <p>Are you sure you want to proceed?</p>

        <template #footer>
            <UiButton variant="ghost" @click="showModal = false"> Cancel </UiButton>
            <UiButton variant="primary" @click="handleConfirm"> Confirm </UiButton>
        </template>
    </UiModal>
</template>

Footer component for modals with cancel and confirm buttons.

Props

PropTypeDefaultDescription
cancelTextstring'Cancel'Cancel button label
confirmTextstring'Confirm'Confirm button label
confirmVariantstring'primary'Button variant: 'primary', 'secondary', 'ghost', 'danger', 'danger-ghost', etc.
isLoadingbooleanfalseDisables buttons and shows loading state
isDisabledbooleanfalseDisables confirm button

Slots

  • default - Custom footer content (overrides default buttons)

Events

  • cancel - Cancel button clicked
  • confirm - Confirm button clicked

Badge (UiBadge)

Status indicator with variants.

Props

PropTypeDefaultDescription
variantstring'default''default', 'success', 'warning', 'error', 'neutral'
sizestring'sm''sm', 'md'
dotbooleanfalseShow dot instead of background

Slots

  • default - Badge text
  • icon - Icon before text

Examples

<UiBadge variant="success">Active</UiBadge>
<UiBadge variant="warning">Pending</UiBadge>
<UiBadge variant="error">Failed</UiBadge>

<!-- With dot -->
<UiBadge variant="success" dot>Online</UiBadge>

Tabs (UiTabs)

Tab navigation with keyboard support.

Props

PropTypeDefaultDescription
modelValuestring-v-model (active tab value)
tabsarray[{ value, label, count? }]

Examples

<UiTabs
    v-model="activeTab"
    :tabs="[
        { value: 'all', label: 'All', count: 25 },
        { value: 'active', label: 'Active', count: 20 },
        { value: 'draft', label: 'Drafts', count: 5 },
    ]"
/>

Toggle (UiToggle)

Toggle switch using icons.

Props

PropTypeDefaultDescription
modelValuebooleanfalsev-model value
labelstring-Accessible label
sizestring'md''sm', 'md'
disabledbooleanfalseDisable toggle

Examples

<UiToggle v-model="enabled" label="Enable feature" />

Empty State (UiEmptyState)

Placeholder for empty data.

Props

PropTypeDefaultDescription
iconstring-Nuxt/Lucide icon name (e.g. lucide:inbox), rendered via UiIconBox
titlestringrequiredMain heading
descriptionstring-Secondary text

Slots

  • action - CTA button

Examples

<UiEmptyState
    icon="lucide:mail"
    title="No emails yet"
    description="Create your first email template to get started."
>
  <template #action>
    <UiButton variant="primary">
      <template #iconLeft>
        <Icon name="lucide:plus" class="w-4 h-4" />
      </template>
      Create Template
    </UiButton>
  </template>
</UiEmptyState>

Error Boundary (UiErrorBoundary)

Catches errors from child components and displays a fallback UI with optional retry.

Props

PropTypeDefaultDescription
fallbackMessagestring'Something went wrong. Please try again.'Custom fallback message
showRetrybooleantrueWhether to show a retry button

Slots

  • default - Content to render (caught by error boundary)

Examples

<UiErrorBoundary fallback-message="Failed to load contacts.">
  <ContactList />
</UiErrorBoundary>

Error Alert (UiErrorAlert)

Alert banner for displaying error, warning, info, or success messages.

Props

PropTypeDefaultDescription
messagestringrequiredAlert message text
titlestring-Custom title (defaults to variant-based title)
variantstring'error''error', 'warning', 'info', 'success'

Examples

<UiErrorAlert message="Failed to save changes." />

<UiErrorAlert variant="warning" message="Your domain DNS is not yet verified." />

<UiErrorAlert variant="success" message="Campaign sent successfully!" />

Confirmation Dialog (UiConfirmationDialog)

Confirm/cancel modal with customizable title, description, and variant styles.

Props

PropTypeDefaultDescription
openboolean-Controls dialog visibility
titlestring'Are you sure?'Dialog title
descriptionstring'This action cannot be undone.'Dialog description
confirmTextstring'Confirm'Confirm button text
cancelTextstring'Cancel'Cancel button text
variantstring'default''danger', 'warning', 'default'
isLoadingbooleanfalseShows loading spinner on confirm button
persistentbooleanfalsePrevents closing by clicking backdrop

Slots

  • default - Custom content above action buttons

Events

  • update:open - Dialog visibility changed
  • confirm - Confirm button clicked
  • cancel - Cancel button clicked

Examples

<UiConfirmationDialog
    v-model:open="showDeleteDialog"
    title="Delete Contact"
    description="This will permanently remove the contact and all associated data."
    variant="danger"
    confirm-text="Delete"
    :is-loading="isDeleting"
    @confirm="handleDelete"
/>

Contextual menu that avoids overflow clipping.

Props

PropTypeDefaultDescription
openbooleanfalsev-model:open
positionstring'right''left', 'right'

Slots

  • trigger - Button that opens menu
  • default - Menu items
  • UiDropdownMenuItem - Menu item with icon, disabled, danger props
  • UiDropdownDivider - Separator line

Examples

<UiDropdownMenu v-model:open="menuOpen">
  <template #trigger>
    <button class="btn btn-ghost p-2">
      <Icon name="lucide:more-vertical" class="w-4 h-4" />
    </button>
  </template>

  <UiDropdownMenuItem icon="lucide:pencil" @click="handleEdit">
    Edit
  </UiDropdownMenuItem>
  <UiDropdownMenuItem icon="lucide:copy" @click="handleDuplicate">
    Duplicate
  </UiDropdownMenuItem>
  <UiDropdownDivider />
  <UiDropdownMenuItem icon="lucide:trash-2" danger @click="handleDelete">
    Delete
  </UiDropdownMenuItem>
</UiDropdownMenu>

Menu item for use inside UiDropdownMenu.

Props

PropTypeDefaultDescription
iconstring-Nuxt/Lucide icon name (e.g. lucide:pencil), rendered via <Icon :name>
disabledbooleanfalseDisables the menu item
dangerbooleanfalseApplies danger styling

Slots

  • default - Menu item label

Events

  • click - Item clicked

Separator line for use inside UiDropdownMenu. No props.

Selectable List Item (UiSelectableListItem)

List item with radio or checkbox selection.

Props

PropTypeDefaultDescription
labelstringrequiredItem label text
descriptionstring-Optional description
typestring'radio''radio' or 'checkbox'
modelValuestring / boolean / string-Current selected value
valuestring-Item value
namestring-Input name attribute
disabledbooleanfalseDisables the item

Examples

<UiSelectableListItem
    v-model="selectedPlan"
    value="pro"
    label="Pro Plan"
    description="Up to 10,000 contacts"
    name="plan"
/>

Segmented Control (UiSegmentedControl)

Tab-like segmented selector with animated indicator.

Props

PropTypeDefaultDescription
optionsarrayrequired[{ value, label, disabled? }]
modelValuestring-Currently selected value
sizestring'md''sm', 'md'

Slots

  • option-{value} - Custom rendering for a specific option (receives option and active props)

Examples

<UiSegmentedControl
    v-model="view"
    :options="[
        { value: 'grid', label: 'Grid' },
        { value: 'list', label: 'List' },
    ]"
/>

Stat Card (UiStatCard)

Statistics display card with value, label, and variant.

Props

PropTypeDefaultDescription
valuestring / numberrequiredStatistic value
labelstringrequiredStatistic label
variantstring'default''default', 'success', 'warning', 'error', 'secondary'

Examples

<UiStatCard value="1,234" label="Total Contacts" />
<UiStatCard value="98.5%" label="Delivery Rate" variant="success" />
<UiStatCard value="12" label="Bounces" variant="error" />

Step Indicator (UiStepIndicator)

Progress indicator showing steps with completion status and connector lines.

Props

PropTypeDefaultDescription
stepsarrayrequired[{ id, label, number }]
getStepStatusfunctionrequired(stepId: string) => 'completed' | 'current' | 'upcoming'
isConnectorHighlightedfunctionrequired(index: number) => boolean — determines connector highlighting

Examples

<UiStepIndicator
    :steps="[
        { id: 'domain', label: 'Add Domain', number: 1 },
        { id: 'dns', label: 'Configure DNS', number: 2 },
        { id: 'verify', label: 'Verify', number: 3 },
    ]"
    :get-step-status="getStatus"
    :is-connector-highlighted="(i) => i < currentStep"
/>

Toast (useToast)

Global toast notifications via composable.

Usage

const { showToast } = useToast();

// Success (default)
showToast('Saved successfully');

// Error
showToast('Failed to save', 'error');

Toasts auto-dismiss after 3 seconds. Multiple toasts stack vertically.

Icon Box (UiIconBox)

A rounded container holding a single icon, used for empty states, list rows, and section headers. Renders the icon through Nuxt's <Icon>.

Props

PropTypeDefaultDescription
iconstringrequiredNuxt/Lucide icon name (e.g. lucide:users)
sizestring'md''xs', 'sm', 'md', 'lg', 'xl'
variantstring'brand''brand', 'success', 'warning', 'error', 'surface', 'inverse'
roundedstring'xl''lg', 'xl', '2xl', 'full'

Examples

<UiIconBox icon="lucide:users" />

<UiIconBox icon="lucide:check" variant="success" size="sm" rounded="full" />

<UiIconBox icon="lucide:inbox" size="xl" variant="surface" rounded="2xl" />

Setup

UiToast is mounted once in apps/web/app/app.vue for global availability. It is wrapped in <ClientOnly> to avoid an SSR hydration mismatch:

<template>
    <div>
        <NuxtLayout>
            <NuxtPage />
        </NuxtLayout>

        <!-- Global toast notifications (client-only to avoid SSR hydration mismatch) -->
        <ClientOnly>
            <UiToast />
        </ClientOnly>
    </div>
</template>

Theme Toggle (UiThemeToggle)

A single button that cycles the color scheme through System -> Light -> Dark. It uses @nuxtjs/color-mode under the hood and persists the preference automatically. The icon (monitor / sun / moon) reflects the current mode. There are no props; an optional default slot lets you append a text label next to the icon.

Examples

<!-- Icon-only toggle -->
<UiThemeToggle class="p-2 rounded-lg hover:bg-bg-surface" />

<!-- With a label -->
<UiThemeToggle class="flex items-center gap-2">
  Theme
</UiThemeToggle>

Chart kit

Hand-rolled SVG charts on design tokens — no chart library. Four components share the same rules:

  • One axis only. Never dual-scale; if two series need different scales, use two charts.
  • Single series = brand hue (--color-brand, the default color on every component).
  • Multi-series/categorical = the fixed-order palette --chart-cat-1--chart-cat-4 (CVD- and contrast-validated in both themes). Assign colors in order — never shuffle to "look nice".
  • Text in text tokens, never series colors. Labels and values are text-text-* / fill-text-tertiary; color belongs to marks only.
  • Recessive grids. Grid lines use --chart-grid, dashed, 0.5 stroke; the baseline is solid.
  • Tabular numerals everywhere a number can change width.

Trend Chart (UiTrendChart)

Line/area time-series with a recessive dashed grid, y-axis max/mid/min labels, first/last x labels, an endpoint dot, and a crosshair hover with a tooltip.

Props

PropTypeDefaultDescription
data{ label, value }[]requiredOrdered series (labels are categorical)
colorstringvar(--color-brand)Line/marker color
showAreabooleantrue10%-opacity fill under the line
formatValue(value: number) => stringcompact (1.2k)Axis + tooltip number format
ariaLabelstring'Trend chart'Accessible name for the SVG

Examples

<UiTrendChart
    :data="days.map((d) => ({ label: d.label, value: d.opens }))"
    aria-label="Opens per day over the last 30 days"
/>

<!-- Second categorical series on its own chart, palette order preserved -->
<UiTrendChart :data="clicksSeries" color="var(--chart-cat-2)" :show-area="false" />

Sparkline (UiSparkline)

Inline mini polyline (no axes, no grid) with an endpoint dot, for list rows and stat tiles. ariaLabel is required — the sparkline itself is purely visual.

Props

PropTypeDefaultDescription
datanumberrequiredOrdered values
ariaLabelstringrequiredAccessible summary of the trend
colorstringvar(--color-brand)Line color
widthnumber88Width in px
heightnumber26Height in px

Examples

<UiSparkline :data="weeklyOpens" aria-label="Opens trending up over the last 8 weeks" />

Stat Tile (UiStatTile)

The chart-kit stat: uppercase muted label, Instrument Serif display numeral (tabular-nums), optional delta line. Use it when the number is the hero of a chart surface; keep UiStatCard for plain colored stat values.

Props

PropTypeDefaultDescription
labelstringrequiredUppercase tile label
valuestring / numberrequiredDisplay value (pre-formatted)
deltastring-Delta text, e.g. '12% vs last week'
deltaDirection'up' / 'down' / 'flat''flat'up = success, down = error, flat = tertiary

Examples

<UiStatTile label="Delivered" value="12,480" delta="8% vs last week" delta-direction="up" />
<UiStatTile label="Bounces" :value="bounces" delta="2 more than usual" delta-direction="down" />

Bars (UiBars)

Thin vertical bars anchored to the baseline: rounded data ends, 2px gaps, per-bar tooltip on hover and keyboard focus (opacity-only — zero layout shift), optional sparse x labels. Zero values render as a 2px baseline stub in --chart-grid.

Props

PropTypeDefaultDescription
data{ label, value }[]requiredOrdered bars
colorstringvar(--color-brand)Bar fill
heightnumber128Plot height in px (labels add below)
labelEverynumber0Show every Nth x label (last always shown); 0 = none
formatValue(value: number) => stringtoLocaleString()Tooltip / aria value format
ariaLabelstring'Bar chart'Accessible name for the chart group

Examples

<UiBars
    :data="sendVolume.map((d) => ({ label: d.label, value: d.count }))"
    :label-every="1"
    :format-value="(v) => `${v.toLocaleString()} emails`"
    aria-label="Emails sent per day over the last 7 days"
/>

Helpers

The pure geometry/format helpers behind the kit live in packages/ui/utils/chart.ts (computeChartPoints, computeYBounds, buildAreaPath, formatChartValue, …) and are unit-tested; reuse them for bespoke SVG charts instead of re-deriving scales.

Micro-interaction kit

Reusable Fluid Functionalism motion drop-ins, defined in packages/ui/assets/css/motion.css and imported by the design-system barrel — available in every app without extra setup. All of them consume the shared motion tokens (--motion-fast/--motion-moderate, --ease-spring) — never hand-written milliseconds — and collapse to opacity-only (or nothing) under prefers-reduced-motion.

Hover reveal (.ui-hover-reveal)

Opacity-only reveal of an action cluster when its row is hovered or focused. The cluster stays in the DOM at full size (opacity, not display) so revealing it never shifts layout, and pointer-events gate with the reveal so invisible actions are never tappable on touch devices. Trigger via Tailwind's group class or an explicit .ui-hover-reveal-host on the row; keyboard focus inside the row (:focus-within) reveals it too.

<!-- DO: cluster inside a hovered/focused host; absolute so it overlays -->
<li class="group relative">
  <span>{{ item.title }}</span>
  <div class="ui-hover-reveal absolute right-3 top-1/2 -translate-y-1/2 flex gap-0.5">
    <button type="button" aria-label="Archive">…</button>
  </div>
</li>
<!-- DON'T: v-if / display toggles shift layout and drop keyboard access -->
<li @mouseenter="show = true" @mouseleave="show = false">
  <div v-if="show">…</div>
</li>

Spring press (.ui-press)

A scale: 0.98 press on :active, riding --motion-fast + --ease-spring. For bespoke interactive elements (icon buttons, cards acting as buttons) — .btn already carries its own press.

<!-- DO -->
<button type="button" class="ui-press p-2 rounded-lg hover:bg-bg-surface">…</button>
<!-- DON'T: hand-rolled press with hardcoded timing, or doubling up on .btn
     (which already presses) -->
<button class="btn btn-primary ui-press">…</button>
<button class="active:scale-95 transition-transform duration-100">…</button>

Staggered entrance (.ui-stagger)

Put on a list container: direct children enter with opacity + a 6px rise at 20ms steps on the moderate tier. Meant for short lists (up to 8 items — queues, action decks, dashboards); later items share the last delay so long lists never feel sluggish. Under reduced motion items simply appear.

<!-- DO: a short, meaningful queue -->
<ul class="ui-stagger">
  <li v-for="task in todaysTasks" :key="task.id">…</li>
</ul>
<!-- DON'T: hundreds of virtualized rows — the animation replays on every
     scroll-in and delays reading the data -->
<ul class="ui-stagger"><li v-for="msg in allMessages" :key="msg.id">…</li></ul>

Proximity emphasis (.ui-proximity)

For dense icon rows (toolbars, quick-action clusters): the hovered target lifts slightly, its immediate neighbours stay near-full opacity, the rest recede — focus follows the pointer, CSS-only. Keyboard focus (:focus-visible) gets the same emphasis.

<!-- DO: a dense, single-purpose icon row -->
<div class="ui-proximity flex items-center gap-1">
  <button type="button" aria-label="Reply">…</button>
  <button type="button" aria-label="Archive">…</button>
  <button type="button" aria-label="Snooze">…</button>
</div>
<!-- DON'T: on mixed content — receding siblings reads as disabling them -->
<nav class="ui-proximity">…text links and headings…</nav>

Number ticker (UiNumberTicker)

Animated numeral for stat tiles and counters: when value changes, the displayed number rolls to the new value over one --motion-moderate beat. Always tabular-nums so digits never jitter horizontally. Under prefers-reduced-motion it renders plain text that simply updates.

Props

PropTypeDefaultDescription
valuenumberrequiredTarget value; changes animate
formatter(value: number) => stringrounded + groupedDisplay format for the in-flight value

Examples

<!-- DO: hero numeral that updates live -->
<p class="text-2xl font-semibold"><UiNumberTicker :value="stats.sentToday" /></p>

<!-- Custom format -->
<UiNumberTicker :value="deliveryRate" :formatter="(n) => `${(n * 100).toFixed(1)}%`" />
<!-- DON'T: static values that never change (plain text is simpler), or
     inside sentences where rolling digits distract from reading -->
<p>You have <UiNumberTicker :value="3" /> drafts.</p>