kabzvue

A Vue.js component library with Tailwind CSS styling
19,585
Install
npm install kabzvue
Latest Version:4.3.3
Vue:3
License:MIT
Last Updated:Jul 9, 2026
Links: GitHub  ·  npm
Maintainer: kabz

STL Horizon Vue Atomic UI Library

A comprehensive Vue.js component library with Tailwind CSS styling, featuring automated testing, semantic versioning, and CI/CD pipelines.

Development & Release Workflow

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Run tests
npm run test:unit

# Run tests with coverage
npm run test:coverage

# Build for production
npm run build

# Preview production build
npm run preview

Docs Full Gallery

The docs now include a permanent full component gallery route:

  • /components/full-gallery

How to use it:

  1. Open docs at /components/components
  2. Click Open Full Gallery
  3. Interact with component props live and preview changes immediately

For local docs development:

npm run dev:docs

Commit Convention

This project uses Conventional Commits for automatic semantic versioning:

# Features (minor version bump: 1.2.3 → 1.3.0)
git commit -m "feat: add new component"

# Bug fixes (patch version bump: 1.2.3 → 1.2.4)
git commit -m "fix: resolve button click issue"

# Breaking changes (major version bump: 1.2.3 → 2.0.0)
git commit -m "feat!: redesign component API

BREAKING CHANGE: component props have changed"

# Other changes (patch version bump)
git commit -m "docs: update component documentation"
git commit -m "refactor: improve component performance"
git commit -m "test: add component tests"

Automatic Versioning & Publishing

The project uses GitHub Actions for automated semantic versioning and NPM publishing:

  • Triggers: Push to main branch after tests pass
  • Version Bumps:
    • major: Breaking changes (BREAKING CHANGE or !: in commit)
    • minor: New features (feat: commits)
    • patch: Bug fixes (fix: commits) or other changes
  • Process:
    1. Run comprehensive test suite
    2. Analyze commits since last release
    3. Bump version automatically
    4. Create git tag and commit
    5. Publish to NPM with public access

Testing

This library includes comprehensive testing with both unit and integration tests.

Test Structure

src/__tests__/
├── *.spec.js                    # Unit tests for individual components
└── integration/                 # Integration tests for component combinations
    ├── FormIntegration.spec.js
    ├── ModalIntegration.spec.js
    ├── CardIntegration.spec.js
    └── NavigationIntegration.spec.js

Testing Types

Unit Tests
  • Individual component functionality
  • Props, events, and styling validation
  • Accessibility testing
  • Error handling
Integration Tests
  • Form Integration: Complete form workflows with Input, Button, Select, Checkbox
  • Modal Integration: Modal with form submission and alert interactions
  • Card Integration: Cards with headers, content, footers, and actions
  • Navigation Integration: Dropdown menus and breadcrumb navigation

CI/CD Pipeline

The project uses GitHub Actions for automated testing and releases:

  • Triggers: Push/PR to main and develop branches
  • Node.js versions: 18.x, 20.x, 22.x
  • Checks: Linting, unit tests, integration tests, build verification
  • Coverage: Uploaded to Codecov with 70% threshold requirements
  • Release: Automatic semantic versioning and NPM publishing

Coverage Requirements

  • Branches: 70%
  • Functions: 70%
  • Lines: 70%
  • Statements: 70%

Running Tests Locally

# Run all tests
npm run test:unit

# Run tests once (CI mode)
npm run test:unit:run

# Run with coverage
npm run test:coverage

# Run only integration tests
npm run test:unit -- --testPathPattern=integration

# Run specific test file
npm run test:unit -- Button.spec.js

Test Configuration

  • Framework: Vitest with jsdom environment
  • Coverage: V8 provider with HTML, JSON, and text reports
  • Mocking: FontAwesome icons and other external dependencies
  • Assertions: Built-in Vitest matchers

Writing Tests

When adding new components, follow these patterns:

  1. Unit Tests: Test component props, events, slots, and styling
  2. Integration Tests: Test component combinations and workflows
  3. Accessibility: Include ARIA attributes and keyboard navigation tests
  4. Edge Cases: Test disabled states, loading states, and error conditions

Example test structure:

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Component from '../components/Component.vue'

describe('Component', () => {
  it('renders with default props', () => {
    const wrapper = mount(Component)
    expect(wrapper.exists()).toBe(true)
  })

  it('handles user interactions', async () => {
    const wrapper = mount(Component)
    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted('click')).toHaveLength(1)
  })
})

Component Usage Documentation

This document provides comprehensive usage examples and API reference for all Vue components and directives in the STL Horizon Vue Atomic UI library.

Button Authorization Access Control

Button supports access-control props directly:

  • permission: string | string[] | boolean | () => boolean
  • role: string | string[] | boolean | () => boolean
  • requireAll: boolean (used for permission arrays)
  • unauthorized: 'show' | 'hide' | 'disable' (default: 'show')

Configure auth resolver

import { createApp } from 'vue'
import VueUI from 'kabzvue'
import 'kabzvue/css'
import App from './App.vue'

const app = createApp(App)

app.use(VueUI, {
  authResolver: ({ permission, role, requireAll }) => {
    // Return true if user can access this button
    return myCanAccess(permission, role, requireAll)
  },
})

Usage examples

<Button permission="update.compliance" unauthorized="hide">Save</Button>

<Button
  :permission="['risk.create', 'risk.update']"
  :requireAll="true"
  unauthorized="disable"
>
  Submit
</Button>

<Button :role="['super-admin', 'admin']" unauthorized="hide">Delete</Button>

Notes

  • Without permission/role, Button works as normal.
  • unauthorized="hide" removes the button from render output.
  • unauthorized="disable" keeps it visible but disabled.
  • If no authResolver is configured, restricted buttons default to visible.

Tooltip Directive

A custom Vue directive for displaying tooltips on hover with customizable positioning and styling.

Usage

<template>
  <div>
    <!-- Basic tooltip -->
    <Button v-tooltip="'This is a tooltip'">Hover me</Button>

    <!-- Tooltip with position modifier -->
    <Button v-tooltip.bottom="'Tooltip at bottom'">Hover me</Button>
    <Button v-tooltip.left="'Tooltip on left'">Hover me</Button>
    <Button v-tooltip.right="'Tooltip on right'">Hover me</Button>
    <Button v-tooltip.top="'Tooltip on top'">Hover me</Button>
  </div>
</template>

<script setup>
import Button from '@/components/Button.vue'
</script>

Position Modifiers

  • v-tooltip or v-tooltip.top - Tooltip appears above the element
  • v-tooltip.bottom - Tooltip appears below the element
  • v-tooltip.left - Tooltip appears to the left of the element
  • v-tooltip.right - Tooltip appears to the right of the element

Features

  • Automatic positioning: Tooltips are positioned automatically based on available space
  • Smooth animations: Fade in/out transitions for better UX
  • Arrow indicators: Visual arrows pointing to the target element
  • Accessibility: Proper ARIA attributes and keyboard support
  • Customizable styling: Black background with white text, easily customizable via CSS
  • Event handling: Shows on mouseenter, hides on mouseleave and click

Styling

The tooltip uses the following default styles:

  • Background: Semi-transparent black (rgba(0, 0, 0, 0.9))
  • Text color: White
  • Padding: 6px 12px
  • Border radius: 4px
  • Font size: 14px
  • Z-index: 9999

You can customize the appearance by targeting the .tooltip-custom class in your CSS.

Implementation Details

The directive creates a tooltip element dynamically and positions it relative to the viewport. It includes cleanup on component unmount to prevent memory leaks.

or else import in the main.js as

app.directive('tooltip', tooltip)

Table of Contents

Accordion

An expandable/collapsible component for organizing content into sections.

Props

Prop Type Default Description
items Array - Array of accordion items with title and content
modelValue Number | Array null Currently expanded item(s) index(es)
multiple Boolean false Allow multiple items to be expanded
variant String 'default' Visual style (default, bordered, filled, flush)
size String 'md' Size variant (sm, md, lg)
disabled Boolean false Whether the accordion is disabled

Events

Event Payload Description
update:modelValue Number | Array Emitted when expanded items change
change { index, expanded } Emitted when an item is toggled

Slots

Slot Description
header-{index} Custom header content for specific item
content-{index} Custom content for specific item
content Fallback content slot

Example

<template>
  <Accordion
    v-model="expandedItems"
    :items="accordionItems"
    variant="bordered"
    size="lg"
    multiple
  />
</template>

<script setup>
import { ref } from 'vue'
import Accordion from '@/components/Accordion.vue'

const expandedItems = ref([0])
const accordionItems = [
  {
    id: 'item1',
    title: 'What is Vue?',
    content: 'Vue is a progressive JavaScript framework for building user interfaces.'
  },
  {
    id: 'item2',
    title: 'Why use Vue?',
    content: 'Vue offers a gentle learning curve, excellent performance, and great ecosystem.'
  }
]
</script>

AccordionItem

A single expandable item for use within an accordion structure.

Props

Prop Type Default Description
variant String 'default' Visual style (default, bordered, filled, flush)
size String 'md' Size variant (sm, md, lg)

Slots

Slot Description
header Header content
content Content to show when expanded

Example

<template>
  <AccordionItem variant="bordered">
    <template #header>
      <h3>Custom Header</h3>
    </template>
    <template #content>
      <p>This is the content that expands.</p>
    </template>
  </AccordionItem>
</template>

<script setup>
import AccordionItem from '@/components/AccordionItem.vue'
</script>

Avatar

A component for displaying user avatars with image, initials, or icon fallback.

Props

Prop Type Default Description
src String - Image source URL
alt String - Alt text for image
initials String - User initials to display
size String 'md' Size variant (xs, sm, md, lg, xl)
variant String 'default' Visual style (default, outline)

Example

<template>
  <div class="flex space-x-4">
    <Avatar src="/path/to/avatar.jpg" alt="User Avatar" />
    <Avatar initials="JD" size="lg" />
    <Avatar variant="outline" />
  </div>
</template>

<script setup>
import Avatar from '@/components/Avatar.vue'
</script>

Breadcrumb

A navigation component showing the current page's location in a hierarchy.

Props

Prop Type Default Description
items Array - Array of breadcrumb items with label and optional href
separator String 'chevron-right' Icon name for separator
ariaLabel String 'Breadcrumb navigation' Accessibility label
variant String 'default' Visual style (default, ghost)

Example

<template>
  <Breadcrumb :items="breadcrumbItems" />
</template>

<script setup>
import Breadcrumb from '@/components/Breadcrumb.vue'

const breadcrumbItems = [
  { label: 'Home', href: '/' },
  { label: 'Products', href: '/products' },
  { label: 'Electronics' }
]
</script>

ButtonGroup

A container component for grouping related buttons together.

Props

Prop Type Default Description
orientation String 'horizontal' Layout direction (horizontal, vertical)
size String 'default' Size variant (sm, default, lg)
variant String 'default' Visual style (default, outline, ghost)
ariaLabel String - Accessibility label for the group

Slots

Slot Description
default Button components

Example

<template>
  <ButtonGroup variant="outline" aria-label="Text formatting">
    <Button>Bold</Button>
    <Button>Italic</Button>
    <Button>Underline</Button>
  </ButtonGroup>
</template>

<script setup>
import ButtonGroup from '@/components/ButtonGroup.vue'
import Button from '@/components/Button.vue'
</script>

Calendar

A standalone calendar component for date selection with month navigation.

Props

Prop Type Default Description
id String - Custom ID
modelValue String | Date - Selected date
disabled Boolean false Whether disabled
required Boolean false Whether required
min String | Date - Minimum selectable date
max String | Date - Maximum selectable date
placeholder String 'Select date' Placeholder text
format String 'MM/DD/YYYY' Date format
clearable Boolean true Show clear button
showToday Boolean true Show today button
calendarPosition String 'left-0' Calendar dropdown position
ariaDescribedby String - ARIA describedby attribute

Events

Event Payload Description
update:modelValue String Emitted when date changes

Example

<template>
  <Calendar
    v-model="selectedDate"
    placeholder="Pick a date"
    format="YYYY-MM-DD"
    :min="'2023-01-01'"
    :max="'2023-12-31'"
  />
</template>

<script setup>
import { ref } from 'vue'
import Calendar from '@/components/Calendar.vue'

const selectedDate = ref('')
</script>

Divider

A visual separator component for dividing content sections.

Props

Prop Type Default Description
orientation String 'horizontal' Orientation (horizontal, vertical)
size String 'md' Size variant (sm, md, lg)
color String 'gray' Color variant (gray, primary, secondary)
faded Boolean false Whether to apply opacity
label String - Label text for horizontal dividers
spacing String - Custom spacing classes

Example

<template>
  <div>
    <p>Content above</p>
    <Divider />
    <p>Content below</p>

    <Divider orientation="vertical" class="h-6" />

    <Divider label="Section Break" />
  </div>
</template>

<script setup>
import Divider from '@/components/Divider.vue'
</script>

Dropdown

A dropdown menu component with customizable trigger and placement.

Props

Prop Type Default Description
triggerText String 'Options' Text for default trigger
placement String 'bottom-start' Dropdown position (bottom-start, bottom-end, top-start, top-end)
variant String 'default' Trigger button variant (default, outline, ghost)

Slots

Slot Description
trigger Custom trigger content
default Dropdown menu items

Example

<template>
  <Dropdown>
    <template #trigger>
      <Button variant="outline">Menu</Button>
    </template>
    <DropdownItem @click="action1">Action 1</DropdownItem>
    <DropdownItem @click="action2">Action 2</DropdownItem>
    <DropdownItem variant="danger" @click="delete">Delete</DropdownItem>
  </Dropdown>
</template>

<script setup>
import Dropdown from '@/components/Dropdown.vue'
import DropdownItem from '@/components/DropdownItem.vue'
import Button from '@/components/Button.vue'

const action1 = () => console.log('Action 1')
const action2 = () => console.log('Action 2')
const delete = () => console.log('Delete')
</script>

DropdownItem

An individual item within a dropdown menu.

Props

Prop Type Default Description
href String - Link URL (makes item a link)
icon String - Icon name
shortcut String - Keyboard shortcut text
disabled Boolean false Whether disabled
variant String 'default' Visual variant (default, danger)

Events

Event Payload Description
click Event Emitted when item is clicked

Slots

Slot Description
default Item content

Example

<template>
  <DropdownItem icon="user" shortcut="⌘U" @click="profile">
    Profile
  </DropdownItem>
</template>

<script setup>
import DropdownItem from '@/components/DropdownItem.vue'

const profile = () => console.log('Open profile')
</script>

Image

An enhanced image component with loading states, aspect ratios, and error handling.

Props

Prop Type Default Description
src String - Image source URL
alt String '' Alt text
aspectRatio String 'auto' Aspect ratio (auto, square, 16/9, 4/3, 3/2)
objectFit String 'cover' Object fit (cover, contain, fill, none, scale-down)
loading String 'lazy' Loading strategy (lazy, eager)
showSpinner Boolean true Show loading spinner

Events

Event Payload Description
load Event Emitted when image loads
error Event Emitted when image fails to load

Example

<template>
  <div class="grid grid-cols-3 gap-4">
    <Image
      src="/path/to/image1.jpg"
      alt="Image 1"
      aspect-ratio="square"
    />
    <Image
      src="/path/to/image2.jpg"
      alt="Image 2"
      aspect-ratio="16/9"
      object-fit="contain"
    />
  </div>
</template>

<script setup>
import Image from '@/components/Image.vue'
</script>

Slider

A customizable range slider component for selecting numeric values.

Props

Prop Type Default Description
modelValue Number | String 0 The current value of the slider
min Number 0 Minimum value
max Number 100 Maximum value
step Number 1 Step increment

Events

Event Payload Description
update:modelValue Number Emitted when the slider value changes

Example

<template>
  <Slider v-model="volume" :min="0" :max="100" :step="5" />
</template>

<script setup>
import { ref } from 'vue'
import Slider from '@/components/Slider.vue'

const volume = ref(50)
</script>

Input

A versatile input component supporting various types and features.

Props

Prop Type Default Description
modelValue String | Number '' The input value
type String 'text' Input type (text, email, password, number, tel, url, search, date, color)
placeholder String '' Placeholder text
disabled Boolean false Whether the input is disabled
readonly Boolean false Whether the input is readonly
required Boolean false Whether the input is required
size String 'md' Size variant (sm, md, lg)
variant String 'default' Visual variant (default, error, success)
iconLeft String | Object null Icon component for left side
iconRight String | Object null Icon component for right side
clearable Boolean false Show clear button when input has value
clearLabel String 'Clear input' Accessibility label for clear button
autocomplete String null Autocomplete attribute
error String null Error message
id String null Custom ID for the input

Events

Event Payload Description
update:modelValue String | Number Emitted when input value changes
blur Event Emitted on blur
focus Event Emitted on focus
clear - Emitted when clear button is clicked
keydown Event Emitted on keydown

Exposed Methods

  • focus(): Focus the input
  • blur(): Blur the input
  • select(): Select all text in the input

Example

<template>
  <Input
    v-model="email"
    type="email"
    placeholder="Enter your email"
    :iconLeft="MailIcon"
    clearable
    required
  />
</template>

<script setup>
import { ref } from 'vue'
import Input from '@/components/Input.vue'
import MailIcon from '@/components/icons/MailIcon.vue'

const email = ref('')
</script>

DatePicker

A date picker component with calendar dropdown.

Props

Prop Type Default Description
id String - Custom ID
modelValue String | Date - Selected date
disabled Boolean false Whether disabled
required Boolean false Whether required
min String | Date - Minimum selectable date
max String | Date - Maximum selectable date
placeholder String 'Select date' Placeholder text
format String 'MM/DD/YYYY' Date format
clearable Boolean true Show clear button
showToday Boolean true Show today button in calendar
calendarPosition String 'left-0 bottom-full' Calendar dropdown position
ariaDescribedby String - ARIA describedby attribute

Events

Event Payload Description
update:modelValue String Emitted when date changes

Example

<template>
  <DatePicker
    v-model="selectedDate"
    placeholder="Choose a date"
    format="YYYY-MM-DD"
    :min="minDate"
    :max="maxDate"
  />
</template>

<script setup>
import { ref } from 'vue'
import DatePicker from '@/components/DatePicker.vue'

const selectedDate = ref('')
const minDate = '2023-01-01'
const maxDate = '2023-12-31'
</script>

ReusableForm (v2)

A reusable inline form component with optional sectioned layout, field-level slot overrides, and cross-field validation.

New v2 Props

Prop Type Default Description
sections Array [] Section schema. If provided, form renders by sections
showSubmit Boolean true Toggle internal submit/cancel/reset actions row
crossValidate Function null Cross-field validator returning { [fieldName]: error }

New v2 Slots

Slot Scope Description
section-header { section } Custom section header
field-<name> { field, formData, error, update } Override a specific field renderer
actions { formData, isValid, submit } Custom footer actions

New v2 Events

Event Payload Description
form-change formData Emitted on any value change

v2 Example

<ReusableForm
  title="Create Company"
  entity-name="Company"
  :fields="fields"
  :sections="sections"
  :show-submit="false"
  :cross-validate="crossValidate"
  @form-change="onFormChange"
  @submit="onSubmit"
>
  <template #actions="{ submit }">
    <Button variant="default" @click="submit">Save Company</Button>
  </template>
</ReusableForm>

ReusableFormModal

A reusable modal component for creating and editing forms with various field types.

Props

Prop Type Default Description
modelValue Boolean false Modal visibility
modalType String - 'create' or 'edit'
entityName String - Name of the entity being created/edited
fields Array - Array of field configurations
sections Array [] Same section schema as ReusableForm
initialData Object null Initial form data for editing
loading Boolean false Loading state
customValidation Function null Custom validation function
crossValidate Function null Cross-field validation hook
showSubmit Boolean true Toggle internal actions row
modalSize String '4xl' Modal size
modalResizable Boolean false Whether modal is resizable

Events

Event Payload Description
update:modelValue Boolean Modal visibility changes
submit { formData, modalType, originalData } Form submission
close - Modal close
form-change formData Emitted when field values change

Field Configuration

Each field in the fields array should have:

{
  name: 'fieldName', // Required
  label: 'Field Label', // Required
  type: 'text', // Required: text, number, password, textarea, select, checkbox, slider, date, color
  placeholder: 'Placeholder',
  required: false,
  disabled: false,
  min: 0, // For number/slider/date
  max: 100, // For number/slider/date
  step: 1, // For slider
  rows: 3, // For textarea
  options: [{ value: '1', label: 'Option 1' }], // For select
  checkboxLabel: 'Check this', // For checkbox
  format: 'MM/DD/YYYY', // For date
  clearable: true, // For date
  showToday: true, // For date
  calendarPosition: 'left-0 bottom-full', // For date
  validate: (value, formData) => errorMessage || null, // Custom validation
  onChange: (value, formData) => {}, // Change handler
  trailingIcon: 'eye', // Optional right-side inline icon (input-like fields)
  trailingIconTitle: 'Show password', // Optional tooltip/title for trailing icon
  trailingIconAriaLabel: 'Show password', // Optional aria-label for accessibility
  onTrailingIconClick: (formData, field) => {}, // Optional click handler for trailing icon
  errorMessage: 'Custom error message',
  helpText: 'Short helper text shown under the field'
}

Example

<template>
  <ReusableFormModal
    v-model="showModal"
    :modalType="modalType"
    entityName="User"
    :fields="userFields"
    :initialData="editingUser"
    :loading="submitting"
    @submit="handleSubmit"
  />
</template>

<script setup>
import { ref } from 'vue'
import ReusableFormModal from '@/components/ReusableFormModal.vue'

const showModal = ref(false)
const modalType = ref('create')
const editingUser = ref(null)
const submitting = ref(false)

const userFields = [
  {
    name: 'name',
    label: 'Full Name',
    type: 'text',
    required: true
  },
  {
    name: 'email',
    label: 'Email',
    type: 'email',
    required: true
  },
  {
    name: 'age',
    label: 'Age',
    type: 'number',
    min: 18,
    max: 100
  },
  {
    name: 'bio',
    label: 'Biography',
    type: 'textarea',
    rows: 4
  },
  {
    name: 'department',
    label: 'Department',
    type: 'select',
    options: [
      { value: 'engineering', label: 'Engineering' },
      { value: 'design', label: 'Design' },
      { value: 'marketing', label: 'Marketing' }
    ]
  },
  {
    name: 'isActive',
    label: 'Active',
    type: 'checkbox',
    checkboxLabel: 'User is active'
  },
  {
    name: 'rating',
    label: 'Rating',
    type: 'slider',
    min: 1,
    max: 5,
    step: 1
  },
  {
    name: 'birthDate',
    label: 'Birth Date',
    type: 'date',
    format: 'YYYY-MM-DD'
  }
]

const handleSubmit = ({ formData, modalType }) => {
  submitting.value = true
  // Handle form submission
  console.log('Submitted:', formData, modalType)
  submitting.value = false
  showModal.value = false
}
</script>

Button

A versatile button component with multiple variants, sizes, and states.

Props

Prop Type Default Description
variant String 'default' Visual style (default, danger, outline, secondary, ghost, link, success, warning, info, subtle, dark, light, primaryOutline, dangerOutline, successOutline, gradient)
size String 'default' Size variant (default, xs, sm, lg, icon)
disabled Boolean false Whether the button is disabled
loading Boolean false Shows loading spinner
loadingText String null Text to show when loading

Slots

Slot Description
icon Icon content (shown before text when not loading)
default Button text content

Example

<template>
  <div class="space-x-2">
    <Button variant="default">Default</Button>
    <Button variant="outline" size="sm">Outline</Button>
    <Button variant="danger" :loading="true" loadingText="Deleting...">
      Delete
    </Button>
    <Button variant="icon" size="icon">
      <template #icon>
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
        </svg>
      </template>
    </Button>
  </div>
</template>

<script setup>
import Button from '@/components/Button.vue'
</script>

Alert

A notification component for displaying messages with different variants and optional actions.

Props

Prop Type Default Description
variant String 'info' Alert style (info, success, warning, error, default, danger)
title String - Alert title
message String - Alert message
dismissible Boolean false Show dismiss button
dismissLabel String 'Dismiss alert' Accessibility label for dismiss button
showIcon Boolean true Show variant icon
actions Array [] Array of action buttons
autoClose Boolean | Number false Auto-dismiss after delay (ms) or false

Events

Event Payload Description
dismiss - Emitted when alert is dismissed
action action Emitted when an action button is clicked

Slots

Slot Description
default Alert content (replaces message)
actions Custom action buttons

Example

<template>
  <div class="space-y-4">
    <Alert variant="success" title="Success!" message="Your changes have been saved." dismissible />

    <Alert variant="warning" title="Warning" :autoClose="5000">
      This action cannot be undone.
      <template #actions>
        <Button variant="outline" size="sm" @click="confirmAction">Confirm</Button>
      </template>
    </Alert>

    <Alert variant="error" title="Error" dismissible>
      <p>Something went wrong. Please try again.</p>
    </Alert>
  </div>
</template>

<script setup>
import Alert from '@/components/Alert.vue'
import Button from '@/components/Button.vue'

const confirmAction = () => {
  console.log('Action confirmed')
}
</script>

Badge

A small label component for displaying status, tags, or counts with multiple variant options.

Props

Prop Type Default Description
variant String 'default' Visual style (default, secondary, danger, primary, outline, success, warning, info, ghost, link, subtle, dark, light, primaryOutline, dangerOutline, successOutline, gradient)
size String 'md' Size variant (sm, md, lg)
shape String 'rounded' Shape variant (rounded, pill, square)
icon String | Object null Icon component
dismissible Boolean false Show dismiss button
dismissLabel String 'Dismiss' Accessibility label for dismiss button

Events

Event Payload Description
dismiss - Emitted when badge is dismissed

Slots

Slot Description
default Badge content

Example

<template>
  <div class="space-x-2">
    <Badge variant="default">New</Badge>
    <Badge variant="primary">Primary</Badge>
    <Badge variant="secondary">Secondary</Badge>
    <Badge variant="success" size="sm">Active</Badge>
    <Badge variant="warning" shape="pill">Pending</Badge>
    <Badge variant="danger" icon="ExclamationIcon">Error</Badge>
    <Badge variant="info">Info</Badge>
    <Badge variant="ghost">Ghost</Badge>
    <Badge variant="link">Link</Badge>
    <Badge variant="subtle">Subtle</Badge>
    <Badge variant="dark">Dark</Badge>
    <Badge variant="light">Light</Badge>
    <Badge variant="primaryOutline">Primary Outline</Badge>
    <Badge variant="dangerOutline">danger Outline</Badge>
    <Badge variant="successOutline">Success Outline</Badge>
    <Badge variant="gradient">Gradient</Badge>
    <Badge variant="outline" dismissible @dismiss="handleDismiss">
      Dismissible
    </Badge>
  </div>
</template>

<script setup>
import Badge from '@/components/Badge.vue'

const handleDismiss = () => {
  console.log('Badge dismissed')
}
</script>

Card

A container component for grouping related content with optional header, image, and footer.

Props

Prop Type Default Description
variant String 'default' Visual style (default, outlined, elevated, filled)
padding String 'md' Padding size (none, sm, md, lg, xl)
title String - Card title
subtitle String - Card subtitle
image String - Image source URL
imageAlt String 'Card image' Image alt text
hoverable Boolean false Add hover effect
clickable Boolean false Make card clickable
loading Boolean false Loading state

Events

Event Payload Description
click Event Emitted when card is clicked (if clickable)

Slots

Slot Description
header Card header content
image Card image
default Card content
footer Card footer
actions Header actions

Example

<template>
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
    <Card
      title="Card Title"
      subtitle="Card subtitle"
      image="/path/to/image.jpg"
      hoverable
      clickable
      @click="handleCardClick"
    >
      <p>This is the card content.</p>

      <template #footer>
        <Button variant="outline" size="sm">Learn More</Button>
      </template>
    </Card>

    <Card variant="elevated" padding="lg">
      <template #header>
        <div class="flex justify-between items-center">
          <h3 class="text-lg font-semibold">Custom Header</h3>
          <div class="space-x-2">
            <Button variant="ghost" size="sm">Edit</Button>
            <Button variant="ghost" size="sm">Delete</Button>
          </div>
        </div>
      </template>

      <p>Card with custom header and actions.</p>
    </Card>
  </div>
</template>

<script setup>
import Card from '@/components/Card.vue'
import Button from '@/components/Button.vue'

const handleCardClick = () => {
  console.log('Card clicked')
}
</script>

CardBody

A container component for card content with configurable padding.

Props

Prop Type Default Description
variant String 'default' Padding variant (default, compact, spacious)

Slots

Slot Description
default Card body content

Example

<template>
  <Card>
    <CardHeader>
      <CardTitle>Card Title</CardTitle>
    </CardHeader>
    <CardBody>
      <p>This is the main content of the card.</p>
    </CardBody>
    <CardFooter>
      <Button>Action</Button>
    </CardFooter>
  </Card>
</template>

<script setup>
import Card from '@/components/Card.vue'
import CardHeader from '@/components/CardHeader.vue'
import CardTitle from '@/components/CardTitle.vue'
import CardBody from '@/components/CardBody.vue'
import CardFooter from '@/components/CardFooter.vue'
import Button from '@/components/Button.vue'
</script>

CardContent

A container component for card content with reduced top padding.

Slots

Slot Description
default Card content

Example

<template>
  <Card>
    <CardHeader>
      <CardTitle>Card Title</CardTitle>
    </CardHeader>
    <CardContent>
      <p>This content has reduced top padding.</p>
    </CardContent>
  </Card>
</template>

<script setup>
import Card from '@/components/Card.vue'
import CardHeader from '@/components/CardHeader.vue'
import CardTitle from '@/components/CardTitle.vue'
import CardContent from '@/components/CardContent.vue'
</script>

CardFooter

A container component for card footer content.

Slots

Slot Description
default Card footer content

Example

<template>
  <Card>
    <CardBody>
      <p>Card content</p>
    </CardBody>
    <CardFooter>
      <div class="flex justify-end space-x-2">
        <Button variant="outline">Cancel</Button>
        <Button>Save</Button>
      </div>
    </CardFooter>
  </Card>
</template>

<script setup>
import Card from '@/components/Card.vue'
import CardBody from '@/components/CardBody.vue'
import CardFooter from '@/components/CardFooter.vue'
import Button from '@/components/Button.vue'
</script>

CardHeader

A container component for card header content.

Slots

Slot Description
default Card header content

Example

<template>
  <Card>
    <CardHeader>
      <CardTitle>Card Title</CardTitle>
      <p class="text-sm text-muted-foreground">Card subtitle</p>
    </CardHeader>
    <CardBody>
      <p>Card content</p>
    </CardBody>
  </Card>
</template>

<script setup>
import Card from '@/components/Card.vue'
import CardHeader from '@/components/CardHeader.vue'
import CardTitle from '@/components/CardTitle.vue'
import CardBody from '@/components/CardBody.vue'
</script>

CardTitle

A styled title component for card headers.

Slots

Slot Description
default Title content

Example

<template>
  <Card>
    <CardHeader>
      <CardTitle>Important Announcement</CardTitle>
    </CardHeader>
    <CardBody>
      <p>This is an important message.</p>
    </CardBody>
  </Card>
</template>

<script setup>
import Card from '@/components/Card.vue'
import CardHeader from '@/components/CardHeader.vue'
import CardTitle from '@/components/CardTitle.vue'
import CardBody from '@/components/CardBody.vue'
</script>

Checkbox

A customizable checkbox component with label and description support.

Props

Prop Type Default Description
modelValue Boolean | Array false Checkbox value or array of values
value String | Number | Boolean null Value when used in checkbox group
label String null Checkbox label
description String null Additional description text
disabled Boolean false Whether disabled
required Boolean false Whether required
indeterminate Boolean false Indeterminate state
size String 'md' Size variant (sm, md, lg)
variant String 'default' Visual variant (default, error)
id String null Custom ID
error String null Error message

Events

Event Payload Description
update:modelValue Boolean | Array Emitted when value changes
change Boolean Emitted on change

Slots

Slot Description
default Label content (replaces label prop)

Example

<template>
  <div class="space-y-4">
    <!-- Single checkbox -->
    <Checkbox v-model="agree" label="I agree to the terms" required />

    <!-- Checkbox with description -->
    <Checkbox
      v-model="newsletter"
      label="Subscribe to newsletter"
      description="Receive weekly updates about our products"
    />

    <!-- Checkbox group -->
    <div>
      <h4 class="font-medium mb-2">Select your interests:</h4>
      <div class="space-y-2">
        <Checkbox
          v-model="interests"
          value="tech"
          label="Technology"
        />
        <Checkbox
          v-model="interests"
          value="design"
          label="Design"
        />
        <Checkbox
          v-model="interests"
          value="business"
          label="Business"
        />
      </div>
    </div>

    <!-- Indeterminate checkbox -->
    <Checkbox
      v-model="selectAll"
      :indeterminate="partialSelection"
      label="Select all"
      @change="handleSelectAll"
    />
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import Checkbox from '@/components/Checkbox.vue'

const agree = ref(false)
const newsletter = ref(true)
const interests = ref(['tech'])
const selectAll = ref(false)

const partialSelection = computed(() => {
  return interests.value.length > 0 && interests.value.length < 3
})

const handleSelectAll = (checked) => {
  if (checked) {
    interests.value = ['tech', 'design', 'business']
  } else {
    interests.value = []
  }
}
</script>

DataTable

A comprehensive data table component with sorting, pagination, selection, and loading states.

Props

Prop Type Default Description
data Array - Array of data items
columns Array - Column configuration array
title String - Table title
description String - Table description
selectable Boolean false Enable row selection
selectedItems Array [] Currently selected items
sortBy String - Initial sort column
sortOrder String 'asc' Initial sort order
pageSize Number 10 Items per page
showPagination Boolean true Show pagination
striped Boolean false Striped rows
hoverable Boolean true Hover effects
clickableRows Boolean false Make rows clickable
emptyText String 'No data available' Empty state text
rowKey String 'id' Unique key for rows
variant String 'default' Table variant
density String 'normal' Row density
loading Boolean false Loading state
dataLoading Boolean false Data loading state
sortLoading Boolean false Sort loading state
bulkLoading Boolean false Bulk operation loading
rowLoading Object {} Row-specific loading states

Events

Event Payload Description
selection-change selectedItems Selection changed
sort-change { column, direction } Sort changed
row-click rowData Row clicked
page-change page Page changed
page-size-change size Page size changed

Slots

Slot Description
header Table header content
filters Filter controls
cell-{columnKey} Custom cell content
actions Row actions
empty Empty state content
footer Table footer

Example

<template>
  <DataTable
    :data="users"
    :columns="columns"
    title="Users"
    selectable
    :selected-items="selectedUsers"
    @selection-change="selectedUsers = $event"
    @row-click="handleRowClick"
  >
    <template #actions="{ item }">
      <Button variant="ghost" size="sm" @click="editUser(item)">Edit</Button>
      <Button variant="ghost" size="sm" @click="deleteUser(item)">Delete</Button>
    </template>
  </DataTable>
</template>

<script setup>
import { ref } from 'vue'
import DataTable from '@/components/DataTable.vue'
import Button from '@/components/Button.vue'

const selectedUsers = ref([])
const users = ref([
  { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
])

const columns = [
  { key: 'name', label: 'Name', sortable: true },
  { key: 'email', label: 'Email', sortable: true },
  { key: 'role', label: 'Role' }
]

const handleRowClick = (row) => {
  console.log('Row clicked:', row)
}

const editUser = (user) => {
  console.log('Edit user:', user)
}

const deleteUser = (user) => {
  console.log('Delete user:', user)
}
</script>

Select

A styled select dropdown component.

Props

Prop Type Default Description
modelValue String | Number | Boolean '' Selected value
id String null Custom ID
name String null Form name
disabled Boolean false Whether disabled
required Boolean false Whether required

Events

Event Payload Description
update:modelValue String | Number | Boolean Emitted when selection changes

Slots

Slot Description
default Option elements

Example

<template>
  <Select v-model="selectedOption">
    <option value="" disabled>Select an option</option>
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
    <option value="option3">Option 3</option>
  </Select>
</template>

<script setup>
import { ref } from 'vue'
import Select from '@/components/Select.vue'

const selectedOption = ref('')
</script>

Textarea

A multi-line text input component with auto-resize and validation support.

Props

Prop Type Default Description
modelValue String '' Textarea value
placeholder String '' Placeholder text
disabled Boolean false Whether disabled
readonly Boolean false Whether readonly
required Boolean false Whether required
rows Number 3 Number of visible rows
maxlength Number null Maximum character count
resize String 'vertical' Resize behavior (none, vertical, horizontal, both)
autoResize Boolean false Auto-resize to content height
size String 'md' Size variant (sm, md, lg)
variant String 'default' Visual variant (default, error, success)
error String null Error message
id String null Custom ID

Events

Event Payload Description
update:modelValue String Emitted when value changes
blur Event Emitted on blur
focus Event Emitted on focus
keydown Event Emitted on keydown

Exposed Methods

  • focus(): Focus the textarea
  • blur(): Blur the textarea
  • select(): Select all text

Example

<template>
  <Textarea
    v-model="message"
    placeholder="Enter your message..."
    :rows="4"
    :autoResize="true"
    maxlength="500"
    required
  />
</template>

<script setup>
import { ref } from 'vue'
import Textarea from '@/components/Textarea.vue'

const message = ref('')
</script>

Modal

A modal dialog component with backdrop and keyboard support.

Props

Prop Type Default Description
modelValue Boolean - Modal visibility
showClose Boolean true Show close button
closeOnBackdrop Boolean true Close on backdrop click
size String '4xl' Modal size (sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl, 6xl, 7xl, full)
resizable Boolean false Whether modal is resizable

Events

Event Payload Description
update:modelValue Boolean Modal visibility changes
close - Emitted when modal closes

Slots

Slot Description
default Modal content

Example

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

    <Modal v-model="showModal" size="lg">
      <div class="p-6">
        <h2 class="text-lg font-semibold mb-4">Modal Title</h2>
        <p class="mb-4">Modal content goes here.</p>
        <div class="flex justify-end">
          <Button @click="showModal = false">Close</Button>
        </div>
      </div>
    </Modal>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Modal from '@/components/Modal.vue'
import Button from '@/components/Button.vue'

const showModal = ref(false)
</script>

FormField

A form field wrapper component that provides labels, descriptions, and validation messages.

Props

Prop Type Default Description
label String - Field label
description String - Field description
error String - Error message
success String - Success message
helpText String - Help text
required Boolean false Whether field is required
type String 'text' Field type (for password toggle)
modelValue String | Date - Field value
size String 'md' Size variant (sm, md, lg)
id String - Custom ID

Events

Event Payload Description
update:modelValue String | Date Emitted when value changes

Slots

Slot Description
default Field input component

Scoped Slot Props

The default slot receives:

  • fieldId: Unique ID for the field
  • hasError: Boolean indicating if field has error
  • ariaDescribedBy: ARIA describedby attribute
  • showPassword: Boolean for password visibility (password fields only)
  • togglePasswordVisibility: Function to toggle password visibility

Example

<template>
  <FormField
    label="Email Address"
    description="We'll never share your email with anyone else."
    :error="emailError"
    required
  >
    <template #default="{ fieldId, hasError, ariaDescribedBy }">
      <Input
        :id="fieldId"
        v-model="email"
        type="email"
        placeholder="Enter your email"
        :class="hasError ? 'border-red-500' : ''"
        :aria-describedby="ariaDescribedBy"
      />
    </template>
  </FormField>
</template>

<script setup>
import { ref } from 'vue'
import FormField from '@/components/FormField.vue'
import Input from '@/components/Input.vue'

const email = ref('')
const emailError = ref('')
</script>

Label

A styled label component for form inputs.

Props

Prop Type Default Description
htmlFor String - ID of associated input
size String 'md' Size variant (sm, md, lg)
required Boolean false Show required indicator
disabled Boolean false Whether associated input is disabled

Slots

Slot Description
default Label text

Example

<template>
  <div>
    <Label htmlFor="username" required>Username</Label>
    <Input id="username" v-model="username" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Label from '@/components/Label.vue'
import Input from '@/components/Input.vue'

const username = ref('')
</script>

Radio

A radio button component for single selection in a group.

Props

Prop Type Default Description
modelValue Any - Selected value
value Any - Value of this radio button
label String - Radio button label
disabled Boolean false Whether disabled
size String 'md' Size variant (sm, md, lg)

Events

Event Payload Description
update:modelValue Any Emitted when radio is selected

Example

<template>
  <div class="space-y-2">
    <Radio v-model="selected" value="option1" label="Option 1" />
    <Radio v-model="selected" value="option2" label="Option 2" />
    <Radio v-model="selected" value="option3" label="Option 3" />
  </div>
  <p>Selected: {{ selected }}</p>
</template>

<script setup>
import { ref } from 'vue'
import Radio from '@/components/Radio.vue'

const selected = ref('option1')
</script>

Switch

A toggle switch component for boolean values.

Props

Prop Type Default Description
modelValue Boolean - Switch state
disabled Boolean false Whether disabled
size String 'md' Size variant (sm, md, lg)

Events

Event Payload Description
update:modelValue Boolean Emitted when switch toggles

Example

<template>
  <div class="flex items-center space-x-2">
    <Switch v-model="enabled" />
    <Label>Enable feature</Label>
  </div>
  <p>Feature is {{ enabled ? 'enabled' : 'disabled' }}</p>
</template>

<script setup>
import { ref } from 'vue'
import Switch from '@/components/Switch.vue'
import Label from '@/components/Label.vue'

const enabled = ref(false)
</script>

Progress

A progress bar component for showing completion status.

Props

Prop Type Default Description
value Number 0 Current progress value
max Number 100 Maximum value
size String 'default' Size variant (sm, default, md, lg)
variant String 'default' Color variant (default, success, warning, danger)

Example

<template>
  <div class="space-y-4">
    <Progress :value="progress" />
    <Progress :value="75" variant="success" size="sm" />
    <Progress :value="50" variant="warning" />
    <Progress :value="25" variant="danger" size="lg" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Progress from '@/components/Progress.vue'

const progress = ref(60)
</script>

Spinner

A loading spinner component with various sizes and colors.

Props

Prop Type Default Description
size String 'md' Size variant (xs, sm, md, lg, xl)
variant String 'default' Color variant (default, primary, secondary)
label String 'Loading...' Accessibility label

Example

<template>
  <div class="space-y-4">
    <Spinner />
    <Spinner size="lg" variant="primary" />
    <Spinner size="sm" variant="secondary" label="Saving..." />
  </div>
</template>

<script setup>
import Spinner from '@/components/Spinner.vue'
</script>

Icon

A FontAwesome icon component with size and color options.

Props

Prop Type Default Description
icon String - FontAwesome icon name
prefix String 'fas' FontAwesome prefix (fas, far, fab, etc.)
size String 'md' Size variant (xs, sm, md, lg, xl, xxl)
color String - Text color class
ariaLabel String - Accessibility label

Example

<template>
  <div class="space-x-4">
    <Icon icon="user" />
    <Icon icon="heart" prefix="far" color="red-500" />
    <Icon icon="github" prefix="fab" size="lg" ariaLabel="GitHub" />
  </div>
</template>

<script setup>
import Icon from '@/components/Icon.vue'
</script>

RailNav

Secondary rail navigation with active state, sticky viewport-fit layout, and logo caching.

Basic usage

<RailNav
  :items="complianceBodies"
  :active-key="activeCode"
  @select="onSelectBody"
/>

Compliance layout usage

<RailNav
  v-if="route.meta.module === 'compliance'"
  :items="filteredBodies"
  :active-key="route.params.code"
  :show-setup="true"
  setup-label="Compliance Setup"
  :resolve-logo="resolveCompanyLogo"
  @select="(item) => router.push(`/compliance/${item.code}`)"
  @setup-click="() => router.push('/compliance-settings')"
/>

Note: when resolveLogo is provided, logo previews are cached by logoId and in-flight requests are deduped to prevent repeated fetches and visible flicker during compliance route navigation in the same session.

Testing

This library includes comprehensive testing with both unit and integration tests to ensure reliability and maintainability.

Test Structure

src/__tests__/
├── *.spec.js                    # Unit tests for individual components
└── integration/                 # Integration tests for component combinations
    ├── FormIntegration.spec.js
    ├── ModalIntegration.spec.js
    ├── CardIntegration.spec.js
    └── NavigationIntegration.spec.js

Testing Types

Unit Tests
  • Individual component functionality
  • Props, events, and styling validation
  • Accessibility testing
  • Error handling
Integration Tests
  • Form Integration: Complete form workflows with Input, Button, Select, Checkbox
  • Modal Integration: Modal with form submission and alert interactions
  • Card Integration: Cards with headers, content, footers, and actions
  • Navigation Integration: Dropdown menus and breadcrumb navigation

CI/CD Pipeline

The project uses GitHub Actions for automated testing:

  • Triggers: Push/PR to main and develop branches
  • Node.js versions: 18.x, 20.x, 22.x
  • Checks: Linting, unit tests, integration tests, build verification
  • Coverage: Uploaded to Codecov with 70% threshold requirements
  • Release: Automatic NPM publishing on main branch pushes

Coverage Requirements

  • Branches: 70%
  • Functions: 70%
  • Lines: 70%
  • Statements: 70%

Running Tests Locally

# Run all tests
npm run test:unit

# Run tests once (CI mode)
npm run test:unit:run

# Run with coverage
npm run test:coverage

# Run only integration tests
npm run test:unit -- --testPathPattern=integration

# Run specific test file
npm run test:unit -- Button.spec.js

Test Configuration

  • Framework: Vitest with jsdom environment
  • Coverage: V8 provider with HTML, JSON, and text reporters
  • Mocking: FontAwesome icons and other external dependencies
  • Assertions: Built-in Vitest matchers

Writing Tests

When adding new components, follow these patterns:

  1. Unit Tests: Test component props, events, slots, and styling
  2. Integration Tests: Test component combinations and workflows
  3. Accessibility: Include ARIA attributes and keyboard navigation tests
  4. Edge Cases: Test disabled states, loading states, and error conditions

Example test structure:

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Component from '../components/Component.vue'

describe('Component', () => {
  it('renders with default props', () => {
    const wrapper = mount(Component)
    expect(wrapper.exists()).toBe(true)
  })

  it('handles user interactions', async () => {
    const wrapper = mount(Component)
    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted('click')).toHaveLength(1)
  })
})

Related Packages

@stlhorizon/vue-ui

A Vue.js component library with Tailwind CSS styling

26,635 0
@gspa/ui

Global Security Practitioners Alliance Vue component library

1,101 0
@prosync_solutions/ui

Prosync Design System - Vue 3 component library with design tokens

5,312
vue-aura-ui

Vue 3 components library built with Vite and Tailwind CSS

472
@alanlee97/lee-ui

基于 Vue 3 的轻量级 UI 组件库

909

Version History

Version Released Vue License
4.3.3 ^3.5.18 MIT
4.3.2 ^3.5.18 MIT
4.3.1 ^3.5.18 MIT
4.3.0 ^3.5.18 MIT
4.2.0 ^3.5.18 MIT