npm package: @sabrenski/spire-ui-vue
Description: A modern, themeable Vue 3 component library with 57+ components, TypeScript support, and Tailwind CSS v4
Author: Spire UI Contributors
Latest version: 0.3.39

# Spire UI

A modern, themeable Vue 3 component library with 57+ components, built with TypeScript and Tailwind CSS v4.

## Installation

```bash
npm install @sabrenski/spire-ui-vue
# or
pnpm add @sabrenski/spire-ui-vue
# or
yarn add @sabrenski/spire-ui-vue
```

### Peer Dependencies

```bash
npm install vue@^3.0.0
```

### RichTextEditor Setup (Optional)

If you use the `RichTextEditor` component, install the Tiptap dependencies:

```bash
npm install @tiptap/vue-3 @tiptap/starter-kit @tiptap/pm \
  @tiptap/extension-link @tiptap/extension-image @tiptap/extension-placeholder \
  @tiptap/extension-text-align @tiptap/extension-underline @tiptap/extension-color \
  @tiptap/extension-text-style @tiptap/extension-highlight @tiptap/extension-typography \
  @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-cell \
  @tiptap/extension-table-header @tiptap/extension-task-list @tiptap/extension-task-item \
  @tiptap/extension-character-count @tiptap/extension-bubble-menu @tiptap/extension-font-family
```

### PhoneInput Setup (Optional)

If you use the `PhoneInput` component with country flags, install:

```bash
npm install flag-icons
```

## Quick Start

### 1. CSS Setup (Required)

Spire UI uses Tailwind CSS v4's `@theme` directive for theming. Add these imports to your main CSS file:

```css
/* app.css */
@import "@sabrenski/spire-ui-vue/theme.css";
@import "tailwindcss";

/* Required: Tell Tailwind to scan component files for utility classes */
@source "../node_modules/@sabrenski/spire-ui-vue/dist/**/*.js";
```

- **theme.css** - Must be imported **before** `tailwindcss` so Tailwind processes the theme tokens
- **@source** - Required for Tailwind to discover which utility classes the components use

### 2. Vue Plugin Setup

```ts
// main.ts
import { createApp } from 'vue'
import { SpireUI } from '@sabrenski/spire-ui-vue'
import App from './App.vue'

const app = createApp(App)

// Optional: Configure global defaults
app.use(SpireUI, {
  defaults: {
    form: { variant: 'outline', size: 'md' },
    button: { variant: 'primary', size: 'md' },
  }
})

app.mount('#app')
```

```vue
<script setup lang="ts">
import { Button, Badge, Input, Card } from '@sabrenski/spire-ui-vue'
import type { ButtonProps } from '@sabrenski/spire-ui-vue'
</script>

<template>
  <Card>
    <Input placeholder="Enter your email" />
    <Button variant="primary">Subscribe</Button>
  </Card>
</template>
```

## Global Configuration

Spire UI supports global default configuration for components using the Vue plugin pattern. This allows you to set default prop values across your entire application.

### Plugin Setup

```ts
import { createApp } from 'vue'
import { SpireUI } from '@sabrenski/spire-ui-vue'
import type { SpireUIOptions } from '@sabrenski/spire-ui-vue'

const app = createApp(App)

app.use(SpireUI, {
  defaults: {
    // Form components: Input, Textarea, Select, NumberInput, Autocomplete
    form: {
      variant: 'filled',  // 'outline' | 'filled' | 'underline'
      size: 'md',         // 'sm' | 'md' | 'lg'
    },
    // Button component
    button: {
      variant: 'primary', // 'primary' | 'secondary' | 'danger' | 'ghost' | 'contrast' | 'outline'
      size: 'md',         // 'sm' | 'md' | 'lg'
      radius: 'md',       // 'none' | 'sm' | 'md' | 'lg' | 'full'
    },
    // Badge component
    badge: {
      variant: 'solid',   // 'solid' | 'flat' | 'outline'
      color: 'default',   // 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'
      size: 'md',         // 'sm' | 'md' | 'lg'
    },
    // Avatar component
    avatar: {
      size: 'md',         // 'xs' | 'sm' | 'md' | 'lg'
      color: 'default',   // 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
      radius: 'full',     // 'none' | 'sm' | 'md' | 'lg' | 'full'
    },
    // Alert component
    alert: {
      variant: 'flat',    // 'flat' | 'solid'
      color: 'default',   // 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
      radius: 'md',       // 'none' | 'sm' | 'md' | 'lg' | 'full'
    },
    // Spinner component
    spinner: {
      size: 'md',         // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
      variant: 'ring',    // 'ring' | 'dots'
    },
    // Tabs component
    tabs: {
      variant: 'solid',   // 'solid' | 'underline' | 'bordered'
      color: 'default',   // 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
      size: 'md',         // 'sm' | 'md' | 'lg'
      radius: 'md',       // 'none' | 'sm' | 'md' | 'lg' | 'full'
    },
    // Progress component
    progress: {
      variant: 'linear',  // 'linear' | 'circular'
      color: 'primary',   // 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
      size: 'md',         // 'sm' | 'md' | 'lg'
    },
    // Card component
    card: {
      shadow: 'md',       // 'none' | 'sm' | 'md' | 'lg'
      radius: 'lg',       // 'none' | 'sm' | 'md' | 'lg'
    },
    // Icon component
    icon: {
      size: 'md',         // 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
      stroke: 'regular',  // 'thin' | 'light' | 'regular' | 'medium' | 'bold'
    },
  }
} as SpireUIOptions)

app.mount('#app')
```

### Resolution Order

Props are resolved in the following order:
1. **Prop value** - If a prop is explicitly passed to a component, it always takes precedence
2. **Global default** - Value from the plugin configuration
3. **Hardcoded fallback** - Built-in default value in the component

```vue
<!-- Uses global default variant, but overrides size -->
<Input size="lg" />

<!-- Uses both global defaults for variant and size -->
<Input />

<!-- Explicit props always win -->
<Input variant="underline" size="sm" />
```

### Programmatic Configuration

You can also configure defaults programmatically:

```ts
import { configureDefaults, getDefaults, resetDefaults } from '@sabrenski/spire-ui-vue'

// Set defaults at runtime
configureDefaults({
  form: { variant: 'filled' },
  button: { variant: 'secondary' }
})

// Get current defaults
const currentDefaults = getDefaults()

// Reset to empty (use component fallbacks)
resetDefaults()
```

## Components

### Layout & Structure
- **Card** - Container with optional header, body, and footer
- **Divider** - Horizontal or vertical separator
- **Group** - Flex container for grouping elements
- **Modal** - Dialog overlay with customizable placement
- **Popover** - Floating content panel
- **Sidebar** - Collapsible navigation panel
- **Navbar** - Navigation bar with brand, items, and mobile menu

### Forms & Inputs
- **Input** - Text input with validation states
- **Textarea** - Multi-line text input
- **Select** - Dropdown selection with search
- **Autocomplete** - Input with suggestions
- **Checkbox** - Single or group checkboxes
- **Radio** - Radio button groups
- **Switch** - Toggle switch
- **Slider** - Range slider
- **NumberInput** - Numeric input with increment/decrement
- **DatePicker** - Date selection with calendar
- **TimePicker** - Time selection
- **ColorPicker** - Color selection with presets
- **FileInput** - File upload input
- **OTPInput** - One-time password input
- **PhoneInput** - International phone input with country codes
- **RichTextEditor** - WYSIWYG editor powered by TipTap
- **FormField** - Form field wrapper with label and validation

### Data Display
- **Table** - Data table with sorting and pagination
- **DataList** - Key-value list display
- **Badge** - Status indicators and labels
- **Avatar** - User profile images with fallback
- **AvatarGroup** - Stacked avatars
- **Icon** - Icon wrapper with size presets
- **IconPicker** - Icon selection from 9000+ icons
- **Image** - Responsive image with loading states
- **Progress** - Progress bar
- **Spinner** - Loading indicator
- **User** - User info display with avatar
- **Tooltip** - Hover tooltips

### Navigation
- **Tabs** - Tab navigation
- **Breadcrumbs** - Breadcrumb navigation
- **Pagination** - Page navigation
- **Link** - Styled anchor links

### Feedback
- **Alert** - Alert messages with variants
- **Toast** - Toast notifications
- **EmptyState** - Empty state placeholder
- **Error** - Error display

### Typography
- **Heading** - Semantic headings (h1-h6)
- **Text** - Text with size and color variants
- **Label** - Form labels
- **Hint** - Helper text

### Charts
- **LineChart** - Line/area charts
- **BarChart** - Bar charts
- **PieChart** - Pie/donut charts
- **Sparkline** - Compact inline charts
- **StatsCard** - Metric display with trend

### Advanced
- **Accordion** - Collapsible content panels
- **Calendar** - Full calendar view
- **Carousel** - Responsive carousel/slider
- **EventsCalendar** - Events calendar with day/week/month views
- **Dropdown** - Dropdown menu with items and sections
- **Rating** - Star rating input
- **Timer** - Countdown/stopwatch timer
- **Tree** - Hierarchical tree view

## Composables

```ts
import { useToast, useDarkMode, useTheme, useRovingFocus, useVirtualScroll, useComponentDefault } from '@sabrenski/spire-ui-vue'
```

- **useToast** - Toast notification management
- **useDarkMode** - Dark mode management with system preference detection
- **useTheme** - Color scheme theme management
- **useRovingFocus** - Keyboard navigation for lists
- **useVirtualScroll** - Virtualized scrolling for large lists
- **useAnchor** - CSS anchor positioning
- **useTrigger** - Trigger/popover positioning
- **useScrollDirection** - Scroll direction detection
- **useChart** - Chart utilities
- **useComponentDefault** - Resolve component defaults from global config

### useDarkMode

Manage dark mode with system preference detection and localStorage persistence.

```vue
<script setup>
import { useDarkMode } from '@sabrenski/spire-ui-vue'

const { isDark, toggle, set, clearPreference } = useDarkMode()
</script>

<template>
  <button @click="toggle">
    {{ isDark ? 'Light Mode' : 'Dark Mode' }}
  </button>
</template>
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storageKey` | `string` | `'spire-dark-mode'` | localStorage key for persistence |
| `initialValue` | `boolean` | System preference | Initial dark mode state |
| `selector` | `string` | `'html'` | Element to apply dark class |
| `darkClass` | `string` | `'dark'` | CSS class for dark mode |
| `listenToSystemChanges` | `boolean` | `true` | React to system preference changes |

| Return | Type | Description |
|--------|------|-------------|
| `isDark` | `Ref<boolean>` | Reactive dark mode state |
| `toggle` | `() => void` | Toggle dark mode on/off |
| `set` | `(value: boolean) => void` | Set dark mode explicitly |
| `clearPreference` | `() => void` | Clear stored preference and follow system |

### useTheme

Manage color scheme themes with localStorage persistence.

```vue
<script setup>
import { useTheme } from '@sabrenski/spire-ui-vue'

const { schemes, currentScheme, setScheme } = useTheme()
</script>

<template>
  <select v-model="currentScheme">
    <option v-for="scheme in schemes" :key="scheme" :value="scheme">
      {{ scheme }}
    </option>
  </select>
</template>
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storageKey` | `string` | `'spire-theme-scheme'` | localStorage key for persistence |
| `initialScheme` | `ThemeScheme` | `'default'` | Initial color scheme |
| `selector` | `string` | `'html'` | Element to apply theme class |
| `customSchemes` | `string[]` | `[]` | Additional custom schemes |

| Return | Type | Description |
|--------|------|-------------|
| `schemes` | `ComputedRef<ThemeScheme[]>` | All available scheme names |
| `currentScheme` | `Ref<ThemeScheme>` | Current active scheme |
| `setScheme` | `(scheme: ThemeScheme) => void` | Set the color scheme |
| `clearPreference` | `() => void` | Clear stored preference and reset to default |

Available schemes: `'default'`, `'ocean'`, `'forest'`, `'sunset'`, `'rose'`, `'midnight'`

## Component Examples

### Button

```vue
<Button variant="primary" size="md" :loading="isLoading">
  Submit
</Button>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'contrast' \| 'outline'` | `'primary'` | Visual style |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
| `disabled` | `boolean` | `false` | Disable interactions |
| `loading` | `boolean` | `false` | Show loading spinner |
| `iconOnly` | `boolean` | `false` | Icon-only mode |
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML button type |

### Badge

```vue
<Badge content="New" color="primary" />

<Badge content="3" color="danger" placement="top-right">
  <Button>Notifications</Button>
</Badge>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `content` | `string \| number` | - | Badge content |
| `variant` | `'solid' \| 'flat' \| 'outline'` | `'solid'` | Visual style |
| `color` | `'default' \| 'primary' \| 'secondary' \| 'success' \| 'warning' \| 'danger' \| 'info'` | `'default'` | Color |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size |
| `dot` | `boolean` | `false` | Show as dot |

### Icon

```vue
<script setup>
import { Icon } from '@sabrenski/spire-ui-vue'
</script>

<template>
  <Icon name="home-01" size="md" />
</template>
```

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `name` | `string` | *required* | Icon name from Hugeicons |
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | Size (12-40px) |
| `stroke` | `'thin' \| 'light' \| 'regular' \| 'medium' \| 'bold'` | `'regular'` | Stroke width |

See [Icon System Guide](./docs/icon-system.md) for advanced configuration including custom adapters and icon registries.

### Toast

```vue
<script setup>
import { useToast, ToastContainer } from '@sabrenski/spire-ui-vue'

const toast = useToast()

function showToast() {
  toast.success('Operation completed!')
}
</script>

<template>
  <Button @click="showToast">Show Toast</Button>
  <ToastContainer />
</template>
```

## Theming

### CSS Imports

Spire UI provides two CSS exports:

| Import | Purpose |
|--------|---------|
| `@sabrenski/spire-ui-vue/theme.css` | Raw `@theme` tokens for Tailwind v4 to process |
| `@sabrenski/spire-ui-vue/style.css` | Pre-built component styles (optional) |

For Tailwind v4 projects:

```css
/* app.css */
@import "@sabrenski/spire-ui-vue/theme.css";
@import "tailwindcss";

/* Required: Scan component files for utility classes */
@source "../node_modules/@sabrenski/spire-ui-vue/dist/**/*.js";
```

You can also import individual theme files:

```css
@import "@sabrenski/spire-ui-vue/theme/base.css";      /* Color palette */
@import "@sabrenski/spire-ui-vue/theme/semantic.css";  /* Semantic tokens + dark mode */
@import "@sabrenski/spire-ui-vue/theme/components.css"; /* Component-specific tokens */
```

### Dark Mode

Add the `dark` class to `<html>` or any parent element:

```html
<html class="dark">
```

### Theme Variants

- **Light** (default)
- **Dark** (`class="dark"`)
- **Ocean** (`class="ocean"`)
- **Forest** (`class="forest"`)
- **Sunset** (`class="sunset"`)
- **Rose** (`class="rose"`)
- **Midnight** (`class="midnight"`)

### Customizing Colors

Override the base color variables to customize the entire palette:

```css
:root {
  --color-primary-base: oklch(55% 0.2 250);  /* Blue primary */
  --color-danger-base: oklch(55% 0.22 27);
}
```

Or override individual semantic tokens:

```css
:root {
  --color-primary: oklch(0.6 0.2 250);
  --color-primary-hover: oklch(0.55 0.2 250);
}
```

## TypeScript

Full TypeScript support with exported types:

```ts
import type {
  ButtonProps,
  BadgeProps,
  AlertProps,
  CardProps,
  ModalProps,
  ToastOptions,
  SpinnerProps,
  TooltipProps,
  // Global configuration types
  SpireUIOptions,
  SpireUIDefaults,
} from '@sabrenski/spire-ui-vue'
```

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## License

MIT