Component Library

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

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

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', '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>
    <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>
    <Search class="w-4 h-4" />
  </template>
</UiInput>

Select (UiSelect)

Native select with consistent styling.

Props

PropTypeDefaultDescription
optionsarray[{ value, label }]
modelValuestring/number-v-model value
labelstring-Label text
placeholderstring-Placeholder (first disabled option)
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"
/>

Radio Group (UiRadioGroup)

Radio button group with orientation options.

Props

PropTypeDefaultDescription
modelValuestring-v-model value
optionsarray[{ value, label, description? }]
namestringrequiredRadio group name
labelstring-Group label (legend)
orientationstring'vertical''vertical', 'horizontal'
disabledbooleanfalseDisable all options

Examples

<UiRadioGroup
    v-model="plan"
    name="plan"
    label="Select a plan"
    :options="[
        { value: 'free', label: 'Free', description: 'Up to 1,000 contacts' },
        { value: 'pro', label: 'Pro', description: 'Up to 10,000 contacts' },
        { value: 'enterprise', label: 'Enterprise', description: 'Unlimited' },
    ]"
/>

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'
closablebooleantrueShow close button
persistentbooleanfalseDisable backdrop click close

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>

Header component for modals with title, optional subtitle, icon, and close button.

Props

PropTypeDefaultDescription
titlestringrequiredHeader title
subtitlestring-Optional subtitle text
iconstring-Lucide icon name
iconClassstring'bg-bg-surface'CSS classes for icon container
iconTextClassstring'text-brand'CSS classes for icon color
closablebooleantrueShow close button

Slots

  • title-suffix - Content after title text
  • actions - Actions area before close button

Events

  • close - Close button clicked

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
iconComponent-Lucide icon component
titlestringrequiredMain heading
descriptionstring-Secondary text

Slots

  • action - CTA button

Examples

<UiEmptyState
    :icon="Mail"
    title="No emails yet"
    description="Create your first email template to get started."
>
  <template #action>
    <UiButton variant="primary">
      <template #iconLeft>
        <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">
      <MoreVertical class="w-4 h-4" />
    </button>
  </template>

  <UiDropdownMenuItem :icon="Edit" @click="handleEdit">
    Edit
  </UiDropdownMenuItem>
  <UiDropdownMenuItem :icon="Copy" @click="handleDuplicate">
    Duplicate
  </UiDropdownMenuItem>
  <UiDropdownDivider />
  <UiDropdownMenuItem :icon="Trash2" danger @click="handleDelete">
    Delete
  </UiDropdownMenuItem>
</UiDropdownMenu>

Menu item for use inside UiDropdownMenu.

Props

PropTypeDefaultDescription
iconstring-Lucide 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"
/>

Table (UiTable)

Data table with sorting and custom cells.

Props

PropTypeDefaultDescription
columnsarrayColumn definitions
dataarrayRow data
loadingbooleanfalseShow loading state
emptyTextstring'No data'Empty state text
hoverablebooleantrueRow hover effect
clickablebooleanfalseClickable rows
rowKeystring'_id'Key field for row identity

Column Definition

interface Column {
    key: string; // Field name in data
    label: string; // Header text
    sortable?: boolean; // Enable sorting
    width?: string; // CSS width
    align?: 'left' | 'center' | 'right';
    format?: (value, row) => string; // Format function
}

Slots

  • header - Above table (filters, search)
  • cell-{key} - Custom cell rendering
  • empty - Custom empty state

Events

  • row-click - Row clicked (when clickable)
  • sort - Column header clicked (when sortable)

Examples

<UiTable
    :columns="[
        { key: 'name', label: 'Name', sortable: true },
        { key: 'email', label: 'Email' },
        { key: 'createdAt', label: 'Created', format: formatDate },
        { key: 'actions', label: '', width: '50px' },
    ]"
    :data="contacts"
    clickable
    @row-click="handleRowClick"
>
  <template #header>
    <div class="px-4 py-3 flex items-center justify-between">
      <h3>Contacts</h3>
      <UiInput v-model="search" placeholder="Search..." size="sm" />
    </div>
  </template>

  <template #cell-actions="{ row }">
    <UiDropdownMenu>
      <!-- ... -->
    </UiDropdownMenu>
  </template>
</UiTable>

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.

Setup

UiToast component is included in app.vue for global availability:

<!-- app.vue -->
<template>
    <NuxtLayout>
        <NuxtPage />
    </NuxtLayout>
    <UiToast />
</template>