cisse-vue-ui
v1.0.0
Published
Vue 3 + TypeScript + Tailwind CSS v4 component library
Maintainers
Readme
cisse-vue-ui
A Vue 3 component library built with TypeScript and Tailwind CSS v4.
Installation
npm install cisse-vue-ui
# or
bun add cisse-vue-uiPeer Dependencies
npm install vue@^3.4 tailwindcss@^4 @iconify/vue@^4Setup
1. Import Styles
Add the pre-compiled CSS to your main CSS file:
@import 'cisse-vue-ui/style.css';
@import 'tailwindcss';2. Configure Primary Color (Optional)
Override the default primary color in your CSS:
@theme {
--color-primary-50: oklch(97% 0.02 142);
--color-primary-100: oklch(94% 0.05 142);
--color-primary-200: oklch(88% 0.10 142);
--color-primary-300: oklch(78% 0.15 142);
--color-primary-400: oklch(65% 0.20 142);
--color-primary-500: oklch(55% 0.22 142);
--color-primary-600: oklch(48% 0.20 142);
--color-primary-700: oklch(40% 0.17 142);
--color-primary-800: oklch(32% 0.14 142);
--color-primary-900: oklch(25% 0.10 142);
--color-primary-950: oklch(18% 0.08 142);
}Usage
Tree-Shaken Imports (Recommended)
<script setup lang="ts">
import { Button, CardComponent, FormInput } from 'cisse-vue-ui'
</script>Category Imports
import { Button, Tabs, TabPanel } from 'cisse-vue-ui/components/core'
import { FormInput, FormSelect, Switch } from 'cisse-vue-ui/components/form'
import { Modal, Alert, LoadingSpinner } from 'cisse-vue-ui/components/feedback'
import { BaseLayout, PageLayout } from 'cisse-vue-ui/components/layout'Global Registration (Vue Plugin)
import { createApp } from 'vue'
import { VueTailwindUI } from 'cisse-vue-ui'
const app = createApp(App)
// Register all components
app.use(VueTailwindUI)
// Or with a prefix
app.use(VueTailwindUI, { prefix: 'Ui' }) // <UiButton>, <UiCard>, etc.
// Or specific components only
app.use(VueTailwindUI, { components: ['Button', 'CardComponent'] })Components
Core
| Component | Description |
|-----------|-------------|
| Button | Button with variants (primary, secondary, outline, ghost, danger, success), sizes, icons, loading state |
| CardComponent | Card container with header, content, and footer slots |
| CardWrapper | Advanced card with shadow/border/padding variants, image positions, clickable/selected states, loading skeleton |
| DataTable | Full-featured data table with sorting, selection, pagination, error states, striped/bordered/compact options |
| Table | Atomic table wrapper with context provider for styling (striped, bordered, hover, compact, stickyHeader) |
| Colgroup / Col | Column grouping and column styling |
| Thead / Tbody / Tfoot | Table section wrappers (support multiple sections) |
| Tr | Table row with selected, clickable, disabled states |
| Th | Header cell with sortable, colspan/rowspan, scope, alignment, and sticky column support |
| Td | Data cell with colspan/rowspan, alignment, main column styling, truncate, and custom classes |
| TableHeader | Composed header row with sort and select-all functionality |
| TableRow | Composed data row with selection and type rendering |
| TableFooter | Composed footer row for summary/pagination |
| TableComponent | Alias for DataTable (backwards compatibility) |
| MobileList | Mobile-optimized card-based list with selection support |
| ResponsiveList | Combines MobileList (mobile) and TableComponent (desktop) with automatic breakpoint switching |
| Tabs | Tab navigation with variants (underline, pills, boxed) |
| TabPanel | Tab content panel (use with Tabs) |
| Dropdown | Dropdown menu with items, icons, and dividers |
| Avatar | User avatar with image, initials, or icon fallback |
| AutocompleteComponent | Searchable select with keyboard navigation |
| MenuItem | Navigation menu item with icon, active state detection, flyout submenus, and route support |
| StatusBadge | Colored status indicator badge |
| TableAction | Icon button for table row actions |
| Stepper | Multi-step progress indicator with horizontal/vertical orientation |
| CollapsibleCard | Card that can expand/collapse its content |
| Accordion | Expandable content sections with single/multiple mode |
| AccordionItem | Individual accordion panel (use with Accordion) |
| Breadcrumb | Navigation breadcrumb trail |
| Drawer | Slide-out panel from any edge (left, right, top, bottom) |
| Popover | Floating content panel triggered by click or hover |
| Timeline | Vertical timeline for events/history display |
| Tooltip | Hover tooltip with customizable position |
| DarkModeToggle | Dark mode toggle button with icon variants |
| StatsCard | Statistics display card with icon, value, and trend |
| StatsGrid | Grid layout for multiple StatsCard components |
| FilterTabs | Tabbed filter buttons with counts |
Form
| Component | Description |
|-----------|-------------|
| FormInput | Text input with validation states, sizes (sm/md/lg), and ARIA support |
| FormSelect | Select dropdown with search, multi-select, and validation |
| FormGroup | Form field wrapper with label, help text, and error states |
| FormLabel | Styled form label with required indicator |
| FormHelp | Help/error text for form fields |
| FormSection | Grouped form section with title and description |
| FormActions | Form button container with alignment options |
| InputWrapper | Wrapper for consistent input styling |
| SearchInput | Search input with icon and clear button |
| Switch | Toggle switch with label and description |
| Checkbox | Checkbox with label, description, and indeterminate state |
| CheckboxGroup | Group of related checkboxes with select all |
| Combobox | Multi-select combobox with search and tags |
| TagsInput | Tag input with add/remove functionality |
| TextArea | Multi-line text input with auto-resize |
| DatePicker | Calendar date picker with min/max dates |
| ColorPicker | Color selection with swatches and custom input |
| IconPicker | Icon selection from Iconify with search |
| FileUpload | Drag-and-drop file upload with preview |
| Rating | Star rating input with half-star support |
| Slider | Single value slider input |
| RangeSlider | Dual-handle range slider |
| EmailInput | Email input with validation |
| PasswordInput | Password input with visibility toggle |
| PhoneInput | Phone number input with formatting |
| NumberInput | Numeric input with increment/decrement |
| MoneyInput | Currency input with formatting |
| PercentInput | Percentage input with formatting |
| QuantityInput | Quantity input with +/- buttons |
| URLInput | URL input with validation |
| OTPInput | One-time password input with multiple digits |
Feedback
| Component | Description |
|-----------|-------------|
| Modal | Modal dialog with focus trap, ARIA support, and slots |
| ConfirmDialog | Confirmation modal with customizable actions |
| Alert | Alert banner with variants (info, success, warning, error) |
| Toast | Individual toast notification with auto-dismiss |
| ToastContainer | Toast notification container with positioning |
| LoadingSpinner | Loading indicator with size variants |
| Progress | Progress bar with percentage display |
| Skeleton | Loading placeholder with animation |
| CardSkeleton | Card loading skeleton |
| ListSkeleton | List loading skeleton |
| TableSkeleton | Table loading skeleton |
| PaginationControls | Pagination with page numbers and navigation |
| NotificationList | Notification list container |
| NotificationComponent | Individual notification item |
| EmptyState | Placeholder for empty content with icon and action slot |
Layout
| Component | Description |
|-----------|-------------|
| AuthLayout | Split-panel authentication layout with branding and form sections |
| BaseLayout | App shell with sidebar, header, main content area, and route-aware menu |
| PageLayout | Page wrapper with breadcrumbs |
| PageHero | Hero section with title, description, and action slots |
Type Display
| Component | Description |
|-----------|-------------|
| TextType | Text value display |
| NumberType | Formatted number display |
| DateType | Formatted date display |
| BooleanType | Boolean value display (check/cross icons) |
| BadgeType | Badge value display with colors |
Atomic Table Usage
Build custom tables with granular control using atomic components:
<script setup lang="ts">
import { Table, Thead, Tbody, Tfoot, Tr, Th, Td, Colgroup, Col } from 'cisse-vue-ui'
const data = [
{ id: 1, name: 'John Doe', amount: 1250.00 },
{ id: 2, name: 'Jane Smith', amount: 890.50 },
]
const total = data.reduce((sum, row) => sum + row.amount, 0)
</script>
<template>
<Table striped bordered>
<Colgroup>
<Col width="200px" />
<Col width="150px" />
</Colgroup>
<Thead>
<Tr>
<Th sortable>Name</Th>
<Th align="right">Amount</Th>
</Tr>
</Thead>
<Tbody>
<Tr v-for="row in data" :key="row.id" clickable @click="select(row)">
<Td main>{{ row.name }}</Td>
<Td align="right">{{ row.amount.toFixed(2) }}</Td>
</Tr>
</Tbody>
<Tfoot>
<Tr>
<Td>Total</Td>
<Td align="right">{{ total.toFixed(2) }}</Td>
</Tr>
</Tfoot>
</Table>
</template>Advanced: Colspan, Rowspan & Row Grouping
<Table bordered>
<Thead>
<Tr>
<Th rowspan="2">Product</Th>
<Th colspan="2" align="center">Sales</Th>
</Tr>
<Tr>
<Th align="right">Q1</Th>
<Th align="right">Q2</Th>
</Tr>
</Thead>
<Tbody>
<!-- Row grouping with scope="rowgroup" -->
<Tr>
<Th rowspan="2" scope="rowgroup">Electronics</Th>
<Td align="right">$45,000</Td>
<Td align="right">$52,000</Td>
</Tr>
<Tr>
<Td align="right">$38,000</Td>
<Td align="right">$41,000</Td>
</Tr>
</Tbody>
</Table>All atomic components support v-bind="$attrs" for full customization.
Or use the full-featured DataTable for common use cases:
<script setup lang="ts">
import { DataTable } from 'cisse-vue-ui'
import type { Property } from 'cisse-vue-ui/types'
const properties: Property[] = [
{ name: 'name', label: 'Name', main: true, sortable: true },
{ name: 'email', label: 'Email' },
{ name: 'status', label: 'Active', type: 'boolean' },
]
</script>
<template>
<DataTable
:items="users"
:properties="properties"
selectable
striped
bordered
@select="handleSelect"
@sort="handleSort"
>
<template #action="{ item }">
<Button variant="ghost" size="sm" icon="lucide:edit" />
</template>
</DataTable>
</template>Composables
import {
useNotifications,
useDarkMode,
useExportCSV,
useDropdown,
useModal,
useToast,
useFocusTrap,
useId,
useInputStyles
} from 'cisse-vue-ui/composables'useModal
Manage modal state with data support:
import { useModal } from 'cisse-vue-ui/composables'
// Simple modal
const createModal = useModal()
createModal.open()
createModal.close()
// Modal with data (e.g., for editing)
const editModal = useModal<User>()
editModal.open(selectedUser)
// Access editModal.data.value in template
// With callbacks
const deleteModal = useModal<Item>({
onOpen: (data) => console.log('Opening with:', data),
onClose: () => refetchData()
})<template>
<!-- Use isOpen for v-model binding -->
<Modal v-model="editModal.isOpen.value" title="Edit User">
<FormInput v-model="editModal.data.value.name" label="Name" />
<template #footer>
<Button @click="editModal.close()">Cancel</Button>
<Button variant="primary" @click="save">Save</Button>
</template>
</Modal>
</template>useDropdown
Shared dropdown logic for custom dropdown components (used internally by Dropdown, FormSelect, AutocompleteComponent):
import { useDropdown } from 'cisse-vue-ui/composables'
import { ref } from 'vue'
const triggerRef = ref<HTMLElement>()
const dropdownRef = ref<HTMLElement>()
const {
isOpen,
highlightedIndex,
dropdownStyle,
open,
close,
toggle,
handleKeydown,
scrollToHighlighted,
} = useDropdown(triggerRef, dropdownRef, {
teleport: true,
align: 'left',
gap: 8,
onOpen: () => console.log('Opened'),
onClose: () => console.log('Closed'),
})useNotifications
const { notifications, addNotification, removeNotification } = useNotifications()
addNotification({
type: 'success',
title: 'Saved',
message: 'Your changes have been saved.'
})useDarkMode
const { isDark, toggle, enable, disable } = useDarkMode({
selector: 'html', // Element to add .dark class
storageKey: 'theme', // localStorage key
defaultDark: false // Default state
})useExportCSV
const { exportToCSV } = useExportCSV()
exportToCSV(data, columns, 'export.csv')useToast
Toast notification system with positioning and auto-dismiss:
import { useToast } from 'cisse-vue-ui/composables'
const { toasts, addToast, removeToast, clearToasts } = useToast()
// Add a toast
addToast({
type: 'success',
title: 'Success!',
message: 'Your changes have been saved.',
duration: 5000 // auto-dismiss after 5s
})
// Different toast types
addToast({ type: 'error', title: 'Error', message: 'Something went wrong' })
addToast({ type: 'warning', title: 'Warning', message: 'Please review' })
addToast({ type: 'info', title: 'Info', message: 'New update available' })<template>
<!-- Add ToastContainer to your app root -->
<ToastContainer position="top-right" />
</template>useFocusTrap
Trap focus within a container (used internally by Modal):
import { useFocusTrap } from 'cisse-vue-ui/composables'
import { ref } from 'vue'
const isActive = ref(true)
const { containerRef } = useFocusTrap({
active: isActive,
focusFirst: true, // Focus first focusable element on activate
restoreFocus: true // Restore focus on deactivate
})useId
Generate unique IDs for accessibility (ARIA relationships):
import { useId } from 'cisse-vue-ui/composables'
const { id, related } = useId({ prefix: 'modal' })
// id.value = 'cisse-modal-1'
// related('title') = 'cisse-modal-1-title'
// related('description') = 'cisse-modal-1-description'<template>
<div :id="id" role="dialog" :aria-labelledby="related('title')">
<h2 :id="related('title')">Dialog Title</h2>
</div>
</template>useInputStyles
Centralized input styling for form components with consistent sizing, states, and dark mode:
import { useInputStyles } from 'cisse-vue-ui/composables'
import { ref, computed } from 'vue'
const size = ref<'sm' | 'md' | 'lg'>('md')
const disabled = ref(false)
const invalid = ref(false)
const {
inputClasses, // Classes for input elements
triggerClasses, // Classes for dropdown triggers
wrapperClasses, // Classes for input wrappers
iconClasses, // Classes for input icons
} = useInputStyles({
size,
disabled,
invalid,
focused: computed(() => false)
})Size variants:
sm: Smaller text (text-sm), reduced padding (px-2.5 py-1.5)md: Default size (text-sm), standard padding (px-3 py-2)lg: Larger text (text-base), increased padding (px-4 py-2.5)
Table Composables
Powerful composables for building advanced data tables:
import {
usePagination,
useTableKeyboardNavigation,
useColumnVisibility,
useColumnResize,
usePinnedRows,
useEditableCell,
useVirtualScroll
} from 'cisse-vue-ui/composables'usePagination
Client-side pagination with page size selector support:
import { usePagination } from 'cisse-vue-ui/composables'
import { ref } from 'vue'
const items = ref([/* your data */])
const {
currentPage, // Current page (1-indexed)
pageSize, // Items per page
totalPages, // Total number of pages
paginatedItems, // Items for current page
startIndex, // First item index (0-indexed)
endIndex, // Last item index (0-indexed)
hasNextPage, // Can go forward?
hasPreviousPage, // Can go back?
goToPage, // Navigate to specific page
nextPage, // Go to next page
previousPage, // Go to previous page
setPageSize, // Change items per page
} = usePagination(items, {
initialPage: 1,
initialPageSize: 10,
pageSizes: [10, 25, 50, 100]
})<template>
<DataTable
:items="items"
:properties="properties"
paginated
:page-size="25"
:page-sizes="[10, 25, 50, 100]"
@page-change="handlePageChange"
/>
</template>useTableKeyboardNavigation
Keyboard navigation for accessible tables:
import { useTableKeyboardNavigation } from 'cisse-vue-ui/composables'
import { ref } from 'vue'
const tableRef = ref<HTMLElement>()
const items = ref([/* your data */])
const {
focusedRowIndex, // Currently focused row
focusedCellIndex, // Currently focused cell
handleKeydown, // Keyboard event handler
focusRow, // Focus specific row
focusCell, // Focus specific cell
} = useTableKeyboardNavigation({
tableRef,
items,
onSelect: (index) => console.log('Selected row:', index),
onActivate: (index) => console.log('Activated row:', index),
})<template>
<Table ref="tableRef" @keydown="handleKeydown">
<Tbody>
<Tr
v-for="(item, index) in items"
:key="item.id"
:tabindex="focusedRowIndex === index ? 0 : -1"
:class="{ 'ring-2 ring-primary-500': focusedRowIndex === index }"
>
<Td>{{ item.name }}</Td>
</Tr>
</Tbody>
</Table>
</template>useColumnVisibility
Toggle column visibility with persistence:
import { useColumnVisibility, type Column } from 'cisse-vue-ui/composables'
const columns: Column[] = [
{ key: 'name', label: 'Name', visible: true, required: true },
{ key: 'email', label: 'Email', visible: true },
{ key: 'phone', label: 'Phone', visible: false },
{ key: 'address', label: 'Address', visible: false },
]
const {
visibleColumns, // Columns with visible: true
hiddenColumns, // Columns with visible: false
toggleColumn, // Toggle single column visibility
showColumn, // Show a column
hideColumn, // Hide a column
showAllColumns, // Show all columns
hideAllColumns, // Hide all except required
resetColumns, // Reset to initial state
isColumnVisible, // Check if column is visible
} = useColumnVisibility({
columns,
storageKey: 'my-table-columns' // Optional: persist to localStorage
})<template>
<div class="mb-4 flex gap-2">
<Checkbox
v-for="col in columns"
:key="col.key"
:model-value="isColumnVisible(col.key)"
:disabled="col.required"
:label="col.label"
@update:model-value="toggleColumn(col.key)"
/>
</div>
<Table>
<Thead>
<Tr>
<Th v-for="col in visibleColumns" :key="col.key">{{ col.label }}</Th>
</Tr>
</Thead>
<Tbody>
<Tr v-for="item in items" :key="item.id">
<Td v-for="col in visibleColumns" :key="col.key">
{{ item[col.key] }}
</Td>
</Tr>
</Tbody>
</Table>
</template>useColumnResize
Drag-to-resize columns with constraints:
import { useColumnResize } from 'cisse-vue-ui/composables'
const {
columnWidths, // Map of column key to width
isResizing, // Currently resizing?
resizingColumn, // Which column is being resized
startResize, // Begin resize operation
getColumnWidth, // Get width for column
setColumnWidth, // Set width for column
resetColumnWidth, // Reset column to default
resetAllWidths, // Reset all columns
} = useColumnResize({
defaultWidth: 150,
minWidth: 80,
maxWidth: 500,
storageKey: 'my-table-widths' // Optional: persist to localStorage
})<template>
<Table>
<Thead>
<Tr>
<Th
v-for="col in columns"
:key="col.key"
:style="{ width: getColumnWidth(col.key) + 'px' }"
resizable
:resizing="resizingColumn === col.key"
@resize-start="(e) => startResize(col.key, e)"
>
{{ col.label }}
</Th>
</Tr>
</Thead>
</Table>
</template>usePinnedRows
Pin rows to top or bottom of table:
import { usePinnedRows } from 'cisse-vue-ui/composables'
interface User {
id: number
name: string
email: string
}
const items = ref<User[]>([/* your data */])
const {
pinnedTop, // Items pinned to top
pinnedBottom, // Items pinned to bottom
unpinnedItems, // Items not pinned
isPinned, // Check if item is pinned
getPinPosition, // Get pin position ('top' | 'bottom' | null)
pinToTop, // Pin item to top
pinToBottom, // Pin item to bottom
unpin, // Unpin item
togglePin, // Toggle pin state
clearPinned, // Clear all pinned items
} = usePinnedRows<User>({
items,
keyField: 'id',
maxPinned: 5 // Optional: limit pinned rows
})<template>
<Table>
<Tbody>
<!-- Pinned top rows -->
<Tr
v-for="item in pinnedTop"
:key="item.id"
class="bg-yellow-50 dark:bg-yellow-900/20"
>
<Td>
<Button size="sm" @click="unpin(String(item.id))">Unpin</Button>
</Td>
<Td>{{ item.name }}</Td>
</Tr>
<!-- Regular rows -->
<Tr v-for="item in unpinnedItems" :key="item.id">
<Td>
<Button size="sm" @click="pinToTop(String(item.id), item)">Pin</Button>
</Td>
<Td>{{ item.name }}</Td>
</Tr>
<!-- Pinned bottom rows -->
<Tr
v-for="item in pinnedBottom"
:key="item.id"
class="bg-blue-50 dark:bg-blue-900/20"
>
<Td>
<Button size="sm" @click="unpin(String(item.id))">Unpin</Button>
</Td>
<Td>{{ item.name }}</Td>
</Tr>
</Tbody>
</Table>
</template>useEditableCell
Inline cell editing with validation:
import { useEditableCell } from 'cisse-vue-ui/composables'
interface User {
id: number
name: string
email: string
}
const {
editingCell, // Current cell being edited { rowKey, field }
editingValue, // Current edit value
isEditing, // Is any cell being edited?
isSaving, // Is save in progress?
startEditing, // Start editing a cell
saveEdit, // Save current edit
cancelEdit, // Cancel current edit
getCellValue, // Get original value for cell
} = useEditableCell<User>({
onSave: async ({ rowKey, field, oldValue, newValue }) => {
// Save to backend
await api.updateUser(rowKey, { [field]: newValue })
},
validate: ({ field, newValue }) => {
if (field === 'email' && !newValue.includes('@')) {
return 'Invalid email address'
}
return true
}
})<template>
<Table>
<Tbody>
<Tr v-for="item in items" :key="item.id">
<Td @dblclick="startEditing(String(item.id), 'name', item.name)">
<template v-if="editingCell?.rowKey === String(item.id) && editingCell?.field === 'name'">
<input
v-model="editingValue"
@keydown.enter="saveEdit"
@keydown.escape="cancelEdit"
@blur="saveEdit"
class="border rounded px-2 py-1"
/>
</template>
<template v-else>
{{ item.name }}
</template>
</Td>
</Tr>
</Tbody>
</Table>
</template>useVirtualScroll
Virtual scrolling for large datasets:
import { useVirtualScroll } from 'cisse-vue-ui/composables'
const items = ref([/* 10,000+ items */])
const {
visibleItems, // Items to render
totalHeight, // Total scroll height
offsetY, // Top offset for positioning
startIndex, // First visible item index
endIndex, // Last visible item index
scrollTop, // Current scroll position
onScroll, // Scroll event handler
scrollToIndex, // Scroll to specific item
containerRef, // Ref for scroll container
} = useVirtualScroll({
items,
rowHeight: 48, // Height of each row in px
containerHeight: 500, // Visible container height
overscan: 5 // Extra rows to render (buffer)
})<template>
<div
ref="containerRef"
class="overflow-auto"
:style="{ height: '500px' }"
@scroll="onScroll"
>
<!-- Spacer for total height -->
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<!-- Positioned visible items -->
<div :style="{ transform: `translateY(${offsetY}px)` }">
<Table>
<Tbody>
<Tr
v-for="(item, index) in visibleItems"
:key="item.id"
:style="{ height: '48px' }"
>
<Td>{{ startIndex + index + 1 }}</Td>
<Td>{{ item.name }}</Td>
</Tr>
</Tbody>
</Table>
</div>
</div>
</div>
<!-- Jump to row -->
<Button @click="scrollToIndex(5000)">Jump to row 5000</Button>
</template>Types
import type { Property, Notification, Breadcrumb } from 'cisse-vue-ui/types'
// Table column definition
const columns: Property[] = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email' },
{ key: 'status', label: 'Status', type: 'badge' }
]
// Breadcrumb navigation
const breadcrumbs: Breadcrumb[] = [
{ label: 'Home', to: '/' },
{ label: 'Users', to: '/users' },
{ label: 'Edit' }
]Component Examples
Button
<Button variant="primary" size="md" :loading="isLoading">
Save Changes
</Button>
<Button variant="outline" icon="lucide:plus">
Add Item
</Button>
<Button variant="danger" icon="lucide:trash">
Delete
</Button>Tabs
<script setup>
import { ref } from 'vue'
import { Tabs, TabPanel } from 'cisse-vue-ui'
const activeTab = ref('profile')
const tabs = [
{ key: 'profile', label: 'Profile' },
{ key: 'settings', label: 'Settings' },
{ key: 'notifications', label: 'Notifications' }
]
</script>
<template>
<Tabs v-model="activeTab" :tabs="tabs" variant="underline">
<TabPanel value="profile">Profile content</TabPanel>
<TabPanel value="settings">Settings content</TabPanel>
<TabPanel value="notifications">Notifications content</TabPanel>
</Tabs>
</template>Switch
<Switch
v-model="emailNotifications"
label="Email notifications"
description="Receive email updates about your account"
/>Alert
<Alert variant="success" title="Success!" dismissible>
Your changes have been saved successfully.
</Alert>
<Alert variant="error" title="Error">
Something went wrong. Please try again.
</Alert>Dropdown
<script setup>
import { Dropdown } from 'cisse-vue-ui'
const items = [
{ key: 'edit', label: 'Edit', icon: 'lucide:edit' },
{ key: 'duplicate', label: 'Duplicate', icon: 'lucide:copy' },
{ key: 'divider', divider: true },
{ key: 'delete', label: 'Delete', icon: 'lucide:trash', danger: true }
]
const handleSelect = (item) => {
console.log('Selected:', item.key)
}
</script>
<template>
<Dropdown :items="items" @select="handleSelect">
<template #trigger-label>Actions</template>
</Dropdown>
</template>Stepper
<script setup>
import { ref } from 'vue'
import { Stepper } from 'cisse-vue-ui'
const currentStep = ref('step2')
const steps = [
{ key: 'step1', title: 'Account', description: 'Create account', icon: 'lucide:user' },
{ key: 'step2', title: 'Profile', description: 'Set up profile', icon: 'lucide:settings' },
{ key: 'step3', title: 'Complete', description: 'Ready to go!', icon: 'lucide:check' }
]
</script>
<template>
<Stepper v-model="currentStep" :steps="steps" />
</template>EmptyState
<EmptyState
title="No results found"
message="Try adjusting your search or filters"
icon="lucide:search-x"
>
<template #action>
<Button variant="primary" size="sm">Clear filters</Button>
</template>
</EmptyState>Checkbox
<Checkbox
v-model="accepted"
label="Accept terms"
description="I agree to the terms and conditions"
/>CardWrapper
<script setup>
import { CardWrapper, Button } from 'cisse-vue-ui'
</script>
<template>
<!-- Basic card with title and icon -->
<CardWrapper
title="Dashboard"
description="Overview of your account"
icon="lucide:layout-dashboard"
>
<div class="p-5">
<p>Card content goes here.</p>
</div>
</CardWrapper>
<!-- Card with image -->
<CardWrapper
title="Product"
image="/product.jpg"
image-position="top"
image-height="200px"
>
<div class="p-5">Product description</div>
</CardWrapper>
<!-- Clickable card with accent -->
<CardWrapper
title="Clickable Card"
accent="primary"
clickable
@click="handleClick"
>
<div class="p-5">Click me!</div>
</CardWrapper>
<!-- Card with actions -->
<CardWrapper title="Settings" icon="lucide:settings">
<template #actions>
<Button size="sm" variant="outline">Cancel</Button>
<Button size="sm">Save</Button>
</template>
<div class="p-5">Settings content</div>
<template #footer>
<p class="text-sm text-gray-500">Last updated: Today</p>
</template>
</CardWrapper>
</template>CardWrapper Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| title | string | - | Card title |
| description | string | - | Card description |
| icon | string | - | Header icon (Iconify format) |
| shadow | 'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | Shadow level |
| rounded | 'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full' | 'lg' | Border radius |
| padding | 'none' \| 'sm' \| 'md' \| 'lg' | 'none' | Content padding |
| border | 'none' \| 'default' \| 'primary' \| 'secondary' | 'none' | Border style |
| variant | 'default' \| 'glass' \| 'outline' \| 'flat' | 'default' | Visual variant |
| accent | 'none' \| 'primary' \| 'secondary' \| 'success' \| 'warning' \| 'danger' \| 'info' | 'none' | Colored accent border |
| clickable | boolean | false | Make card clickable with hover effects |
| selected | boolean | false | Selected state with ring indicator |
| disabled | boolean | false | Disabled state |
| image | string | - | Image URL |
| imagePosition | 'top' \| 'bottom' \| 'left' \| 'right' \| 'background' | 'top' | Image position |
| loading | boolean | false | Show loading skeleton |
IconPicker
<script setup>
import { ref } from 'vue'
import { IconPicker } from 'cisse-vue-ui'
const selectedIcon = ref('mdi:heart')
</script>
<template>
<IconPicker
v-model="selectedIcon"
label="Select Icon"
help="Choose an icon for your item"
:collections="['mdi', 'heroicons', 'lucide']"
/>
</template>TableComponent
<script setup>
import { ref } from 'vue'
import { TableComponent } from 'cisse-vue-ui'
const properties = [
{ name: 'name', label: 'Name', main: true },
{ name: 'email', label: 'Email' },
{ name: 'role', label: 'Role', type: 'badge' }
]
const items = [
{ id: 1, name: 'John Doe', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', role: 'User' }
]
// Selection support
const selectedItems = ref(new Set())
const toggleSelect = (id) => {
if (selectedItems.value.has(id)) {
selectedItems.value.delete(id)
} else {
selectedItems.value.add(id)
}
}
</script>
<template>
<TableComponent
:properties="properties"
:items="items"
selectable
:selected-items="selectedItems"
@select="toggleSelect"
@select-all="toggleSelectAll"
>
<template #action="{ item }">
<TableAction icon="lucide:edit" @click="edit(item)" />
<TableAction icon="lucide:trash" variant="danger" @click="delete(item)" />
</template>
</TableComponent>
</template>ResponsiveList
A component that automatically switches between a mobile card layout and a desktop table layout based on screen size.
<script setup>
import { ref } from 'vue'
import { ResponsiveList } from 'cisse-vue-ui'
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
{ key: 'status', label: 'Status' }
]
const items = [
{ id: 1, name: 'John Doe', email: '[email protected]', status: 'active' },
{ id: 2, name: 'Jane Smith', email: '[email protected]', status: 'inactive' }
]
const selectedItems = ref(new Set())
const toggleSelect = (id) => {
if (selectedItems.value.has(id)) {
selectedItems.value.delete(id)
} else {
selectedItems.value.add(id)
}
}
const toggleSelectAll = () => {
if (selectedItems.value.size === items.length) {
selectedItems.value.clear()
} else {
items.forEach(item => selectedItems.value.add(String(item.id)))
}
}
</script>
<template>
<ResponsiveList
:items="items"
:columns="columns"
key-field="id"
selectable
:selected-items="selectedItems"
breakpoint="lg"
@select="toggleSelect"
@select-all="toggleSelectAll"
>
<!-- Mobile view: avatar -->
<template #avatar="{ item }">
<div class="w-10 h-10 rounded-full bg-primary-500 flex items-center justify-center text-white">
{{ item.name[0] }}
</div>
</template>
<!-- Mobile view: content -->
<template #mobileContent="{ item }">
<h3 class="font-semibold">{{ item.name }}</h3>
<p class="text-sm text-gray-500">{{ item.email }}</p>
</template>
<!-- Mobile view: actions -->
<template #mobileActions="{ item }">
<button @click="viewItem(item)">View</button>
</template>
<!-- Desktop table: custom cell rendering -->
<template #cell-name="{ item }">
<span class="font-medium">{{ item.name }}</span>
</template>
<template #cell-status="{ item }">
<span :class="item.status === 'active' ? 'text-green-600' : 'text-red-600'">
{{ item.status }}
</span>
</template>
<!-- Desktop table: actions column -->
<template #actions="{ item }">
<Button size="sm" variant="ghost" @click="edit(item)">Edit</Button>
</template>
<!-- Empty state -->
<template #empty>
<EmptyState title="No items" message="No items to display" />
</template>
</ResponsiveList>
</template>ResponsiveList Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| items | Array | required | Array of items to display |
| columns | Array | required | Column definitions with key or name, label, and optional type |
| keyField | string | 'id' | Field to use as unique key for items |
| selectable | boolean | false | Enable selection mode |
| selectedItems | Set<string> | - | Set of selected item keys |
| selectableFilter | Function | - | Filter function to determine if an item is selectable |
| breakpoint | string | 'lg' | Breakpoint for switching views: 'sm', 'md', 'lg', 'xl', '2xl' |
MobileList
A mobile-optimized card-based list component with selection support.
<script setup>
import { MobileList } from 'cisse-vue-ui'
</script>
<template>
<MobileList
:items="items"
key-field="id"
selectable
:selected-items="selectedItems"
@select="toggleSelect"
@select-all="toggleSelectAll"
>
<template #avatar="{ item }">
<div class="w-12 h-12 rounded-full bg-blue-500" />
</template>
<template #content="{ item }">
<h3>{{ item.name }}</h3>
<p>{{ item.description }}</p>
</template>
<template #actions="{ item }">
<button>View</button>
</template>
</MobileList>
</template>MenuItem
<script setup>
import { useRoute } from 'vue-router'
import { MenuItem } from 'cisse-vue-ui'
const route = useRoute()
const menuItem = {
label: 'Dashboard',
link: '/dashboard',
icon: 'lucide:layout-dashboard'
}
</script>
<template>
<!-- Auto-detect active state from current route -->
<MenuItem :menu-item="menuItem" :current-path="route.path" />
<!-- Or manually control active state -->
<MenuItem :menu-item="menuItem" :active="true" />
</template>AuthLayout
Split-panel layout for authentication pages (login, register, password reset).
<script setup>
import { ref } from 'vue'
import { AuthLayout, type AuthFeature } from 'cisse-vue-ui'
const email = ref('')
const password = ref('')
const features: AuthFeature[] = [
{ icon: 'lucide:shield', text: 'Secure authentication' },
{ icon: 'lucide:zap', text: 'Fast login' },
{ icon: 'lucide:users', text: 'Team collaboration' },
]
function handleSubmit() {
// Handle login
}
</script>
<template>
<AuthLayout
app-name="My App"
app-icon="lucide:box"
headline="Welcome to"
sub-headline="My Application"
description="Sign in to access your account."
:features="features"
form-title="Sign In"
form-subtitle="Enter your credentials"
home-link="/"
>
<form @submit.prevent="handleSubmit" class="space-y-4">
<input v-model="email" type="email" placeholder="Email" class="..." />
<input v-model="password" type="password" placeholder="Password" class="..." />
<button type="submit">Sign In</button>
</form>
<template #form-footer>
<p class="text-center mt-6">
Don't have an account? <a href="/register">Sign up</a>
</p>
</template>
</AuthLayout>
</template>AuthLayout Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| appName | string | '' | Application name displayed in logo |
| appIcon | string | 'lucide:box' | Iconify icon for app logo |
| headline | string | '' | First line of headline |
| subHeadline | string | '' | Second line with decorative underline |
| description | string | '' | Description paragraph |
| features | AuthFeature[] | [] | List of features with icon and text |
| gradientFrom | string | 'from-primary-700' | Tailwind gradient start class |
| gradientVia | string | '' | Tailwind gradient middle class |
| gradientTo | string | 'to-primary-800' | Tailwind gradient end class |
| showDecorations | boolean | true | Show floating decorative shapes |
| showPattern | boolean | true | Show dot pattern overlay |
| underlineColor | string | 'rgba(165, 180, 252, 0.5)' | Sub-headline underline color |
| formTitle | string | '' | Form panel title |
| formSubtitle | string | '' | Form panel subtitle |
| homeLink | string | '/' | Mobile logo link URL |
| brandingAnimation | string | '' | CSS class for branding animation |
| formAnimation | string | '' | CSS class for form panel animation |
AuthLayout Slots
| Slot | Description |
|------|-------------|
| default | Form content (inside the white card) |
| branding-panel | Complete override of the branding panel |
| branding-logo | Custom logo in branding panel |
| branding-headline | Custom headline content |
| branding-features | Custom features list |
| branding-content | Additional content after features |
| mobile-logo | Custom mobile logo |
| form-header | Content above the form card |
| form-footer | Content below the form card |
BaseLayout
<script setup>
import { useRoute } from 'vue-router'
import { BaseLayout } from 'cisse-vue-ui'
const route = useRoute()
const menuItems = [
{ label: 'Dashboard', link: '/', icon: 'lucide:home' },
{ label: 'Users', link: '/users', icon: 'lucide:users' },
{ label: 'Settings', link: '/settings', icon: 'lucide:settings' }
]
</script>
<template>
<BaseLayout
:menu-items="menuItems"
:current-path="route.path"
:show-dark-toggle="true"
menu-position="top"
>
<template #logo>
<img src="/logo.svg" alt="Logo" class="h-8" />
</template>
<RouterView />
</BaseLayout>
</template>BaseLayout Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| menuItems | MenuItemProps[] | [] | Menu items for the sidebar |
| appName | string | 'App' | App/brand name displayed in sidebar |
| appIcon | string | 'lucide:box' | App icon (Iconify icon name) |
| sidebarOpen | boolean | true | Whether sidebar is open (v-model:sidebarOpen) |
| dark | boolean | false | Whether dark mode is enabled (v-model:dark) |
| showDarkToggle | boolean | true | Show dark mode toggle in header |
| sidebarClass | string | 'bg-[#172b4c]...' | CSS classes for sidebar background |
| currentPath | string | - | Current route path for menu active state |
| userName | string | - | User display name |
| userAvatar | string | - | User avatar (initials or image URL) |
| userMenuItems | UserMenuItem[] | [] | User menu dropdown items |
| menuPosition | 'top' \| 'center' \| 'bottom' | 'top' | Menu vertical position in sidebar |
BaseLayout Slots
| Slot | Description |
|------|-------------|
| default | Main content area (or renders RouterView if available) |
| logo | Custom logo in sidebar header |
| menu | Custom menu content (receives currentPath) |
| sidebar-footer | Content at bottom of sidebar |
| header-center | Center content in header |
| header-actions | Action buttons in header (before dark toggle) |
Dark Mode
Components support dark mode via the .dark class on a parent element:
<html class="dark">
<!-- Components will use dark theme -->
</html>Use the useDarkMode composable or implement your own toggle:
const { isDark, toggle } = useDarkMode()License
MIT
