npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nicklabs-ui

v1.0.46

Published

A Vue 3 component library with glassmorphism design, built for modern web applications.

Readme

nicklabs-ui

A Vue 3 component library with glassmorphism design, built for modern web applications.

Version: 1.0.39 | Framework: Vue 3.5+


Table of Contents


Installation

npm install nicklabs-ui
# or
pnpm add nicklabs-ui

Setup

1. Import Styles

In your main entry file (e.g., main.ts):

import { createApp } from 'vue'
import App from './App.vue'

// Required: reset and CSS variables
import 'nicklabs-ui/reset.css'
import 'nicklabs-ui/variables.css'

// Required: component styles
import 'nicklabs-ui/nicklabs-ui.css'

createApp(App).mount('#app')

2. Import Components

Import components individually as needed:

import { NButton, NInput, NModal, useToast } from 'nicklabs-ui'

3. Register All Components (Optional)

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { NickLabsUI } from 'nicklabs-ui'

const app = createApp(App)
app.use(router)
app.use(NickLabsUI, { router })
app.mount('#app')

Passing router enables useBreadcrumb and useRouteModal without any additional setup.


Components


Form Components


NButton

A versatile button supporting multiple visual variants and semantic intents.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "none" \| "solid" \| "outline" \| "ghost" \| "mute" | "solid" | Visual style | | intent | "none" \| "primary" \| "error" \| "success" \| "warning" \| "info" | "none" | Semantic color intent | | size | "sm" \| "md" \| "lg" | "md" | Button size | | radiusSize | "sm" \| "md" \| "lg" \| "xl" \| "full" | "md" | Border radius | | disabled | boolean | false | Disable interaction | | type | "button" \| "submit" \| "reset" | "button" | HTML button type | | square | boolean | false | Equal width/height (icon button) | | padding | string | — | Custom padding override | | width | string | — | Custom width override | | height | string | — | Custom height override |

Usage

<template>
  <!-- Basic -->
  <NButton>Click me</NButton>

  <!-- Variants -->
  <NButton variant="solid" intent="primary">Primary</NButton>
  <NButton variant="outline" intent="success">Success</NButton>
  <NButton variant="ghost" intent="warning">Warning</NButton>
  <NButton variant="mute" intent="error">Danger</NButton>

  <!-- Sizes -->
  <NButton size="sm">Small</NButton>
  <NButton size="md">Medium</NButton>
  <NButton size="lg">Large</NButton>

  <!-- Submit button -->
  <NButton type="submit" intent="primary">Submit</NButton>

  <!-- Disabled -->
  <NButton disabled>Disabled</NButton>

  <!-- Icon button (square) -->
  <NButton square size="sm">
    <svg>...</svg>
  </NButton>
</template>

<script setup>
import { NButton } from 'nicklabs-ui'
</script>

NInput

A full-featured text input with support for password visibility, number controls, and clearing.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | string \| number | — | Bound value (v-model) | | type | string | "text" | HTML input type | | placeholder | string | — | Placeholder text | | disabled | boolean | false | Disable input | | readonly | boolean | false | Read-only mode | | inline | boolean | false | Display title and input on the same line | | clearable | boolean | false | Show clear button | | maxlength | number | — | Maximum character length | | min | number | — | Minimum value (for type="number") | | max | number | — | Maximum value (for type="number") | | title | string | — | Label above the input | | autocomplete | string | — | HTML autocomplete attribute |

Events

| Event | Payload | Description | |-------|---------|-------------| | update:modelValue | string \| number | Value changed | | focus | FocusEvent | Input focused | | blur | FocusEvent | Input blurred | | input | Event | Input event | | change | Event | Change event | | keydown | KeyboardEvent | Key pressed | | clear | — | Clear button clicked |

Usage

<template>
  <NInput v-model="username" placeholder="Enter username" title="Username" />

  <!-- Password with visibility toggle -->
  <NInput v-model="password" type="password" placeholder="Enter password" />

  <!-- Clearable -->
  <NInput v-model="search" placeholder="Search..." clearable />

  <!-- Number with min/max -->
  <NInput v-model="age" type="number" :min="0" :max="120" title="Age" />

  <!-- Inline title -->
  <NInput v-model="username" title="Username" placeholder="Enter username" inline />
</template>

<script setup>
import { ref } from 'vue'
import { NInput } from 'nicklabs-ui'

const username = ref('')
const password = ref('')
const search = ref('')
const age = ref(0)
</script>

NTextarea

Multi-line text input with optional character count display.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | string | — | Bound value (v-model) | | placeholder | string | — | Placeholder text | | disabled | boolean | false | Disable textarea | | readonly | boolean | false | Read-only mode | | rows | number | 4 | Visible row count | | maxLength | number | — | Maximum character count | | title | string | — | Label above the textarea | | showCount | boolean | false | Show character counter | | wrap | "soft" \| "off" | "soft" | Text wrapping behavior | | autofocus | boolean | false | Auto-focus on mount | | autocomplete | string | — | HTML autocomplete attribute |

Events

| Event | Payload | Description | |-------|---------|-------------| | update:modelValue | string | Value changed | | focus | FocusEvent | Textarea focused | | blur | FocusEvent | Textarea blurred | | input | Event | Input event | | change | Event | Change event | | keydown | KeyboardEvent | Key pressed | | paste | ClipboardEvent | Content pasted |

Usage

<template>
  <NTextarea
    v-model="description"
    title="Description"
    placeholder="Enter description..."
    :rows="5"
    :maxLength="500"
    showCount
  />
</template>

<script setup>
import { ref } from 'vue'
import { NTextarea } from 'nicklabs-ui'

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

NCheckbox

Checkbox input supporting both single and multi-option modes.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | boolean \| any[] | — | Bound value (v-model) | | multiple | boolean | false | Enable multi-option mode | | options | Record<string, any>[] | — | Options for multi mode | | disabled | boolean | false | Disable input | | title | string | — | Group label displayed above options | | label | string | — | Text beside checkbox in single mode (overridable by default slot) | | inline | boolean | false | Display title and options on the same line | | direction | "row" \| "column" | "row" | Layout direction for options | | autofocus | boolean | false | Auto-focus on mount | | allowDeselect | boolean | true | Allow deselecting in single mode; set to false to prevent unchecking once checked | | formatLabel | (option: any) => string | (opt) => opt.label | Function to extract display text from an option | | formatValue | (option: any) => any | (opt) => opt.value | Function to extract the value from an option |

Events

| Event | Description | |-------|-------------| | update:modelValue | Value changed | | change | Generic change event | | change:item | Selected OptionItem changed | | change:value | Selected value changed | | change:values | Selected values array changed (multiple mode) |

Usage

<template>
  <!-- Single checkbox -->
  <NCheckbox v-model="agreed" title="I agree to terms" />

  <!-- Multiple checkboxes -->
  <NCheckbox
    v-model="selected"
    multiple
    :options="options"
    direction="column"
    title="Select interests"
  />

  <!-- Inline title -->
  <NCheckbox v-model="selected" multiple :options="options" title="Interests" inline />

  <!-- Custom field names -->
  <NCheckbox
    v-model="selected"
    multiple
    :options="[{ name: 'Apple', id: 'apple' }, { name: 'Banana', id: 'banana' }]"
    :format-label="(opt) => opt.name"
    :format-value="(opt) => opt.id"
  />
</template>

<script setup>
import { ref } from 'vue'
import { NCheckbox } from 'nicklabs-ui'

const agreed = ref(false)
const selected = ref([])
const options = [
  { label: 'Vue', value: 'vue' },
  { label: 'React', value: 'react' },
  { label: 'Angular', value: 'angular' },
]
</script>

NSelect

Dropdown select with search, multi-select, and clear support.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | any \| any[] | — | Bound value (v-model) | | options | OptionItem[] | [] | Dropdown options | | multiple | boolean | false | Allow multiple selection | | searchable | boolean | false | Enable search/filter | | clearable | boolean | false | Show clear button | | placeholder | string | — | Placeholder text | | disabled | boolean | false | Disable input | | title | string | — | Label above dropdown | | multipleDisplay | "count" \| "tags" | "tags" | How selected items display | | formatLabel | (option: any) => string | (opt) => opt.label | Function to extract display text from an option | | formatValue | (option: any) => any | (opt) => opt.value | Function to extract the value from an option |

Events

| Event | Description | |-------|-------------| | update:modelValue | Value changed | | change | Generic change event | | change:item | Selected OptionItem | | change:value | Selected value | | change:values | Selected values array (multiple mode) |

Usage

<template>
  <!-- Basic select -->
  <NSelect v-model="city" :options="cities" placeholder="Select city" title="City" />

  <!-- Searchable + clearable -->
  <NSelect v-model="country" :options="countries" searchable clearable />

  <!-- Multiple selection -->
  <NSelect
    v-model="tags"
    :options="tagOptions"
    multiple
    multipleDisplay="tags"
    title="Tags"
  />

  <!-- Custom keys (API data with non-standard field names) -->
  <NSelect
    v-model="city"
    :options="[{ name: 'Taipei', id: 'taipei' }, { name: 'Tokyo', id: 'tokyo' }]"
    :format-label="(opt) => opt.name"
    :format-value="(opt) => opt.id"
    placeholder="Select city"
  />

  <!-- Combine multiple fields into display text -->
  <NSelect
    v-model="city"
    :options="apiData"
    :format-label="(opt) => `${opt.name} (${opt.code})`"
    :format-value="(opt) => opt.id"
  />
</template>

<script setup>
import { ref } from 'vue'
import { NSelect } from 'nicklabs-ui'

const city = ref('')
const country = ref(null)
const tags = ref([])

const cities = [
  { label: 'Taipei', value: 'taipei' },
  { label: 'Tokyo', value: 'tokyo' },
  { label: 'Seoul', value: 'seoul' },
]
const tagOptions = [
  { label: 'Frontend', value: 'frontend' },
  { label: 'Backend', value: 'backend' },
  { label: 'DevOps', value: 'devops' },
]
</script>

NFileSelect

Drag-and-drop file selector.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | multiple | boolean | false | Allow multiple file selection | | disabled | boolean | false | Disable input | | label | string | — | Button/drop zone label | | hint | string | — | Helper text below | | accept | string | — | Accepted MIME types (e.g. "image/*") | | title | string | — | Label above component |

Events

| Event | Payload | Description | |-------|---------|-------------| | change:file | File | Single file selected | | change:files | File[] | Files selected (multiple mode) |

Usage

<template>
  <NFileSelect
    title="Upload Avatar"
    label="Drop image here or click to browse"
    hint="Accepts PNG, JPG up to 2MB"
    accept="image/*"
    @change:file="handleFile"
  />

  <!-- Multiple files -->
  <NFileSelect multiple accept=".pdf,.doc" @change:files="handleFiles" />
</template>

<script setup>
import { NFileSelect } from 'nicklabs-ui'

function handleFile(file: File) {
  console.log('Selected:', file.name)
}

function handleFiles(files: File[]) {
  console.log('Selected:', files.length, 'files')
}
</script>

NSwitch

Animated toggle switch.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | boolean | false | Bound value (v-model) | | disabled | boolean | false | Disable switch | | size | "sm" \| "md" \| "lg" | "md" | Switch size | | title | string | — | Label above the switch | | inline | boolean | false | Display title and switch on the same line |

Events

| Event | Payload | Description | |-------|---------|-------------| | update:modelValue | boolean | Value changed | | change | boolean | Value changed |

Usage

<template>
  <!-- Basic -->
  <NSwitch v-model="enabled" />

  <!-- With title (stacked) -->
  <NSwitch v-model="enabled" title="Enable notifications" />

  <!-- Inline title -->
  <NSwitch v-model="darkMode" title="Dark mode" size="lg" inline />
</template>

<script setup>
import { ref } from 'vue'
import { NSwitch } from 'nicklabs-ui'

const enabled = ref(false)
const darkMode = ref(false)
</script>

NDatePicker

Date picker with range selection and custom format support.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | modelValue | string \| Date | — | Bound value (v-model) for single date | | start | string | — | Range start (v-model:start) | | end | string | — | Range end (v-model:end) | | placeholder | string | "請選擇日期" | Placeholder text | | disabled | boolean | false | Disable picker | | clearable | boolean | false | Show clear button | | title | string | — | Label above picker | | format | string | "YYYY-MM-DD" | Date format string (supports YYYY, MM, DD, HH, mm) | | range | boolean | false | Enable range selection mode |

Events

| Event | Payload | Description | |-------|---------|-------------| | update:modelValue | string | Single date value changed | | change | string | Date changed | | clear | — | Clear button clicked | | update:start | string | Range start changed | | update:end | string | Range end changed |

Usage

<template>
  <!-- Single date -->
  <NDatePicker v-model="date" title="Select Date" clearable />

  <!-- With time format -->
  <NDatePicker v-model="datetime" format="YYYY-MM-DD HH:mm" title="Date & Time" />

  <!-- Date range -->
  <NDatePicker
    range
    v-model:start="startDate"
    v-model:end="endDate"
    title="Date Range"
  />
</template>

<script setup>
import { ref } from 'vue'
import { NDatePicker } from 'nicklabs-ui'

const date = ref('')
const datetime = ref('')
const startDate = ref('')
const endDate = ref('')
</script>

Data Display


NTable

A type-safe, sortable data table with support for batch selection and action slots.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | columns | NTableColumn[] | [] | Column definitions | | items | T[] | [] | Row data | | bordered | boolean | false | Show borders | | hoverable | boolean | true | Highlight rows on hover | | loading | boolean | false | Show loading state | | emptyTitle | string | "目前沒有資料" | Title when no data | | emptyDescription | string | — | Description when no data | | itemKey | string | "id" | Unique key field |

Events

| Event | Payload | Description | |-------|---------|-------------| | sort | NTableSortState | Column sort changed | | click | T | Row was clicked |

Slots

| Slot | Description | |------|-------------| | batch | Content in the batch selection header column | | actions-header | Custom header text for the actions column (default: "操作") | | actions | Custom action buttons per row (scoped: { item, column, index }) | | empty | Custom empty state content |

NTableColumn Interface

interface NTableColumn {
  key: string       // Data field key
  label: string     // Column header text
  sortable?: boolean
}

Usage

<template>
  <NTable
    :columns="columns"
    :items="users"
    hoverable
    @sort="handleSort"
    @click="handleItemClick"
  >
    <template #actions="{ item }">
      <NButton size="sm" variant="ghost" intent="primary" @click="edit(item)">Edit</NButton>
      <NButton size="sm" variant="ghost" intent="error" @click="remove(item)">Delete</NButton>
    </template>
  </NTable>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { NTable, NButton } from 'nicklabs-ui'
import type { NTableColumn, NTableSortState } from 'nicklabs-ui'

interface User {
  id: number
  name: string
  email: string
  role: string
}

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

const users = ref<User[]>([
  { id: 1, name: 'Alice', email: '[email protected]', role: 'Admin' },
  { id: 2, name: 'Bob', email: '[email protected]', role: 'User' },
])

function handleSort(state: NTableSortState) {
  console.log('Sort by:', state.key, state.order)
}

function handleItemClick(item: User) {
  console.log('Clicked:', item)
}
</script>

NList

A full-featured data management component combining table, pagination, filtering, and CRUD operations.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | — | Section title displayed in hero header | | description | string | — | Section description displayed in hero header | | items | T[] | [] | Data items (T must extend { id: number }) | | columns | NTableColumn[] | [] | Column definitions | | itemKey | string | "id" | Unique key field | | totalItems | number | 0 | Total item count for pagination calculation | | pageSize | number | 10 | Items per page | | maxPageButtons | number | 7 | Visible page buttons | | creatable | boolean | false | Show create button | | createLabel | string | "新增" | Create button label | | updatable | boolean | false | Show row edit button | | updateLabel | string | "編輯" | Edit button label | | deletable | boolean | false | Show row delete button | | deleteLabel | string | "刪除" | Delete button label | | batchDeletable | boolean | false | Enable batch delete with checkboxes | | batchDeleteLabel | string | "批量刪除" | Batch delete button label | | filterable | boolean | false | Show filter button | | filterLabel | string | "篩選" | Filter button label | | refreshable | boolean | false | Show refresh button | | refreshLabel | string | "重新整理" | Refresh button label | | emptyTitle | string | "目前沒有資料" | Empty state title | | emptyDescription | string | "可以點擊上方的按鈕來新增資料或重新整理" | Empty state description | | emptyIcon | string | — | Custom SVG string for empty state |

Events

| Event | Payload | Description | |-------|---------|-------------| | update | T | Row edit clicked | | delete | T | Row delete clicked | | pageChange | number | Page changed | | create | — | Create clicked | | batchDelete | number[], clearSelected: () => void | Batch delete with selected IDs; call clearSelected() after deletion to reset checkboxes | | refresh | — | Refresh clicked | | filter | — | Filter clicked | | click | T | Row clicked | | sort | NTableSortState | Sort changed |

Slots

| Slot | Description | |------|-------------| | toolbar | Extra toolbar content (alongside create/refresh buttons) | | item | Custom cell renderer (scoped: { item, column, index }) | | actions | Custom action buttons per row (scoped: { item, index }) | | actions-header | Custom header text for the actions column |

Usage

<template>
  <NList
    title="User Management"
    :items="users"
    :columns="columns"
    :page-size="20"
    filterable
    updatable
    deletable
    creatable
    refreshable
    @update="handleEdit"
    @delete="handleDelete"
    @create="handleCreate"
    @refresh="loadUsers"
    @filter="openFilter"
  />
</template>

<script setup lang="ts">
import { NList } from 'nicklabs-ui'

// T must extend { id: number }
interface User {
  id: number
  name: string
  email: string
}
</script>

NTag

Semantic tag/badge component.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | intent | "none" \| "primary" \| "success" \| "warning" \| "error" \| "info" | "none" | Color intent | | variant | "solid" \| "light" \| "outline" | "light" | Visual style | | size | "sm" \| "md" \| "lg" | "md" | Tag size | | closable | boolean | false | Show close button | | round | boolean | false | Fully rounded (pill shape) |

Events

| Event | Description | |-------|-------------| | close | Close button clicked |

Usage

<template>
  <NTag intent="success">Active</NTag>
  <NTag intent="warning" variant="outline">Pending</NTag>
  <NTag intent="error" variant="solid" round>Blocked</NTag>
  <NTag intent="info" closable @close="removeTag">Vue 3</NTag>
</template>

<script setup>
import { NTag } from 'nicklabs-ui'
</script>

NEmpty

Empty state placeholder component.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | — | Main message | | description | string | — | Sub-message | | size | "sm" \| "md" \| "lg" | "md" | Component size | | icon | string | — | Custom SVG string |

Usage

<template>
  <NEmpty
    title="No results found"
    description="Try adjusting your search filters"
    size="lg"
  />
</template>

<script setup>
import { NEmpty } from 'nicklabs-ui'
</script>

NCode

Syntax-highlighted code display with copy-to-clipboard.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | code | string | — | Code content to display | | language | string | — | Language for highlighting | | showLineNumbers | boolean | false | Show line numbers |

Usage

<template>
  <NCode
    :code="snippet"
    language="typescript"
    showLineNumbers
  />
</template>

<script setup>
import { NCode } from 'nicklabs-ui'

const snippet = `const greeting = (name: string) => {
  return \`Hello, \${name}!\`
}`
</script>

Modals & Overlays


NModal

A teleported modal dialog.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | show | boolean | false | Visibility (v-model:show) | | title | string | — | Modal title | | width | string | "500px" | Modal width | | closeOnClickOverlay | boolean | true | Close when backdrop clicked | | showClose | boolean | true | Show close button | | zIndex | number | — | Custom z-index |

Events

| Event | Description | |-------|-------------| | update:show | Visibility changed | | close | Modal closed | | open | Modal opened |

Slots

| Slot | Description | |------|-------------| | default | Modal body content | | footer | Footer actions area |

Usage

<template>
  <NButton @click="open">Open Modal</NButton>

  <NModal v-model:show="isOpen" title="Confirm Action" width="400px">
    <p>Are you sure you want to proceed?</p>

    <template #footer>
      <NButton variant="ghost" @click="isOpen = false">Cancel</NButton>
      <NButton intent="primary" @click="confirm">Confirm</NButton>
    </template>
  </NModal>
</template>

<script setup>
import { ref } from 'vue'
import { NModal, NButton } from 'nicklabs-ui'

const isOpen = ref(false)

function open() { isOpen.value = true }
function confirm() {
  isOpen.value = false
}
</script>

NDrawer

A teleported side drawer that slides in from the right or left edge of the viewport.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | show | boolean | false | Visibility (v-model:show) | | title | string | — | Drawer title | | width | string | "380px" | Drawer width | | placement | "right" \| "left" | "right" | Which side to slide in from | | closeOnClickOverlay | boolean | true | Close when backdrop clicked | | showClose | boolean | true | Show close button | | zIndex | number | 1000 | Custom z-index |

Events

| Event | Description | |-------|-------------| | update:show | Visibility changed | | close | Drawer closed | | open | Drawer opened |

Slots

| Slot | Description | |------|-------------| | default | Drawer body content | | title | Override the title area | | footer | Footer actions area (renders footer only when provided) |

Usage

<template>
  <NButton @click="isOpen = true">Open Drawer</NButton>

  <NDrawer v-model:show="isOpen" title="Edit Item">
    <NInput v-model="name" title="Name" placeholder="Enter name" />

    <template #footer>
      <NButton variant="outline" @click="isOpen = false">Cancel</NButton>
      <NButton variant="solid" intent="primary" @click="save">Save</NButton>
    </template>
  </NDrawer>
</template>

<script setup>
import { ref } from 'vue'
import { NDrawer, NButton, NInput } from 'nicklabs-ui'

const isOpen = ref(false)
const name = ref('')

function save() {
  isOpen.value = false
}
</script>

NAlert

Programmatic alert and confirm dialogs via the useAlert composable.

NAlert must be mounted once at the app root level.

Setup

<!-- App.vue -->
<template>
  <RouterView />
  <NAlert />
</template>

<script setup>
import { NAlert } from 'nicklabs-ui'
</script>

Usage via useAlert

See useAlert composable below.


NToast

Notification toasts via the useToast composable.

NToast must be mounted once at the app root level.

Setup

<!-- App.vue -->
<template>
  <RouterView />
  <NToast />
</template>

<script setup>
import { NToast } from 'nicklabs-ui'
</script>

Usage via useToast

See useToast composable below.


NTooltip

Hover/focus tooltip with directional positioning.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | content | string | — | Tooltip text | | position | "top" \| "right" \| "bottom" \| "left" | "top" | Tooltip position | | disabled | boolean | false | Disable tooltip |

Usage

<template>
  <NTooltip content="Delete this item" position="top">
    <NButton variant="ghost" intent="error" square>
      <svg>...</svg>
    </NButton>
  </NTooltip>
</template>

<script setup>
import { NTooltip, NButton } from 'nicklabs-ui'
</script>

NLoading

Loading indicator supporting inline, overlay, and fullscreen modes.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | loading | boolean | true | Show loading state | | title | string | — | Loading message | | variant | "spinner" \| "dots" | "spinner" | Animation style | | overlay | boolean | false | Overlay mode — covers slot content with a backdrop | | fullscreen | boolean | false | Fullscreen mode — covers the entire viewport and blocks interaction |

Usage

<template>
  <!-- Inline: shows spinner while loading, slot content when done -->
  <NLoading :loading="isFetching" title="Loading data...">
    <p>Content loaded!</p>
  </NLoading>

  <!-- Overlay: spinner overlaid on top of slot content -->
  <NLoading :loading="isSubmitting" overlay title="Saving..." variant="dots">
    <div>Form content here</div>
  </NLoading>

  <!-- Fullscreen: blocks entire viewport, no slot needed -->
  <NLoading :loading="isProcessing" fullscreen title="處理中..." />
</template>

<script setup>
import { ref } from 'vue'
import { NLoading } from 'nicklabs-ui'

const isFetching = ref(true)
const isSubmitting = ref(false)
const isProcessing = ref(false)
</script>

Layout


NLayout

Main application shell integrating sidebar and main content area.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | menus | Menu[] | — | Sidebar menu items | | isShowSidebar | boolean | true | Show/hide sidebar | | copyright | string | — | Footer copyright text | | currentPath | string | "" | Active route path |

Events

| Event | Payload | Description | |-------|---------|-------------| | logout | — | Logout triggered | | navigate | MenuChild | Menu item clicked |

Slots

| Slot | Description | |------|-------------| | default | Main page content |

Usage

<template>
  <NLayout
    :menus="menus"
    :current-path="$route.path"
    copyright="© 2025 MyApp"
    @logout="handleLogout"
    @navigate="handleNavigate"
  >
    <RouterView />
  </NLayout>
</template>

<script setup>
import { NLayout } from 'nicklabs-ui'

const menus = [
  {
    icon: '<svg>...</svg>',
    title: 'Dashboard',
    children: [
      { icon: '<svg>...</svg>', title: 'Overview', route: '/dashboard' },
    ],
  },
]
</script>

NNavigation

Top navigation bar with sidebar toggle, fullscreen, and user controls.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | isShowSidebar | boolean | true | Show sidebar toggle button | | isShowFullscreen | boolean | true | Show fullscreen button | | isShowUser | boolean | true | Show user pill | | isShowLogoutButton | boolean | true | Show logout button |

Events

| Event | Description | |-------|-------------| | toggleSidebar | Sidebar toggle clicked | | logout | Logout clicked |


NSidebar

Collapsible side navigation menu with hover-expand behavior.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | isOpen | boolean | false | Sidebar open state | | menus | Menu[] | — | Menu items | | currentPath | string | "" | Active route path | | userName | string | — | Display name shown in user area | | userAvatarUrl | string | — | Avatar image URL |

Events

| Event | Payload | Description | |-------|---------|-------------| | update:isOpen | boolean | Open state changed | | logout | — | Logout triggered | | navigate | MenuChild | Menu item clicked |


NCard

Content card with glassmorphism styling.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | size | "none" \| "sm" \| "md" \| "lg" | "md" | Padding size | | radius | "none" \| "sm" \| "md" \| "lg" \| "xl" | "md" | Border radius |

Usage

<template>
  <NCard size="lg" radius="xl">
    <h2>Card Title</h2>
    <p>Card content goes here.</p>
  </NCard>
</template>

<script setup>
import { NCard } from 'nicklabs-ui'
</script>

NForm

Form wrapper with optional tab navigation and hero section header.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | model | object | {} | Form data object | | disabled | boolean | false | Disable all inputs | | icon | string | — | Header icon (SVG string) | | title | string | — | Form header title | | description | string | — | Form header description | | tabs | string[] | [] | Tab labels; active tab persisted to localStorage |

Events

| Event | Payload | Description | |-------|---------|-------------| | submit | Record<string, any> | Form submitted with model data | | reset | — | Form reset |

Slots

| Slot | Description | |------|-------------| | heroSection | Replace the entire hero header (rarely needed) | | toolbar | Toolbar area in the hero header (buttons, etc.) | | description | Custom description content in hero header | | tab0 | Content for first tab (or only content when no tabs) | | tab1, tab2, ... | Content for subsequent tabs | | footer | Form footer (submit/cancel buttons) |

Usage

<template>
  <NForm
    :model="formData"
    title="User Profile"
    description="Manage your account details"
    :tabs="['Basic Info', 'Security', 'Preferences']"
    @submit="handleSubmit"
  >
    <template #toolbar>
      <NButton variant="ghost" @click="cancel">Cancel</NButton>
    </template>

    <template #tab0>
      <NInput v-model="formData.name" title="Name" />
      <NInput v-model="formData.email" title="Email" />
    </template>

    <template #tab1>
      <NInput v-model="formData.password" type="password" title="Password" />
    </template>

    <template #footer>
      <NButton type="submit" intent="primary">Save</NButton>
    </template>
  </NForm>
</template>

<script setup>
import { reactive } from 'vue'
import { NForm, NInput, NButton } from 'nicklabs-ui'

const formData = reactive({ name: '', email: '', password: '' })

function handleSubmit(model) {
  console.log('Submitted:', model)
}
</script>

NLoginLayout

Full-screen login page wrapper with animated card.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | backgroundImage | string | — | Background image URL (falls back to --bg-gradient) | | logo | string | — | Logo image URL or SVG string | | title | string | — | Application name | | description | string | — | Subtitle/tagline |

Usage

<template>
  <NLoginLayout
    title="MyApp"
    description="Sign in to your account"
    background-image="/images/bg.jpg"
    logo="/images/logo.png"
  >
    <NInput v-model="email" type="email" placeholder="Email" />
    <NInput v-model="password" type="password" placeholder="Password" />
    <NButton type="submit" intent="primary" style="width: 100%">Sign In</NButton>
  </NLoginLayout>
</template>

<script setup>
import { ref } from 'vue'
import { NLoginLayout, NInput, NButton } from 'nicklabs-ui'

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

NBreadcrumb

Breadcrumb navigation driven by useBreadcrumb. No props — reads from the composable's state automatically.

Configure breadcrumbs via useBreadcrumb.

Usage

<template>
  <NBreadcrumb />
</template>

<script setup>
import { NBreadcrumb } from 'nicklabs-ui'
</script>

NPaginate

Pagination control with smart ellipsis.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | totalItems | number | — | Total number of items | | pageSize | number | — | Items per page | | maxPageButtons | number | 7 | Max visible page buttons (min: 5) |

Events

| Event | Payload | Description | |-------|---------|-------------| | onPageChange | number | New page number |

Usage

<template>
  <NPaginate
    :total-items="totalCount"
    :page-size="20"
    :max-page-buttons="7"
    @on-page-change="loadPage"
  />
</template>

<script setup>
import { ref } from 'vue'
import { NPaginate } from 'nicklabs-ui'

const totalCount = ref(350)

function loadPage(page: number) {
  // fetch page data
}
</script>

NHeroSection

Page header with icon, title, description, breadcrumb, and toolbar slot.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | — | Section title | | description | string | — | Section description | | icon | string | — | Icon (SVG string) |

Slots

| Slot | Description | |------|-------------| | toolbar | Toolbar content (buttons, filters, etc.) | | description | Custom description content |

Usage

<template>
  <NHeroSection title="User Management" description="Manage system users" :icon="userIcon">
    <template #toolbar>
      <NButton intent="primary" @click="create">New User</NButton>
    </template>
  </NHeroSection>
</template>

<script setup>
import { NHeroSection, NButton } from 'nicklabs-ui'

const userIcon = '<svg>...</svg>'
</script>

NSideFilter

A slide-out side drawer for filter interfaces.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | — | Open state (v-model:open) | | title | string | — | Drawer title |

Events

| Event | Description | |-------|-------------| | update:open | Open state changed | | close | Drawer closed |

Usage

<template>
  <NButton @click="filterOpen = true">Filters</NButton>

  <NSideFilter v-model:open="filterOpen" title="Filter Results">
    <NSelect v-model="status" :options="statusOptions" title="Status" />
    <NInput v-model="search" placeholder="Search name..." title="Name" />
  </NSideFilter>
</template>

<script setup>
import { ref } from 'vue'
import { NSideFilter, NButton, NSelect, NInput } from 'nicklabs-ui'

const filterOpen = ref(false)
const status = ref(null)
const search = ref('')
</script>

Composables


useToast

Display notification toasts programmatically.

import { useToast } from 'nicklabs-ui'

const { toasts, toast, removeToast } = useToast()

Methods

| Method | Signature | Description | |--------|-----------|-------------| | toast | (message: string, options?: ToastOptions) => void | Show a toast | | toast.success | (message: string, options?) => void | Success toast | | toast.danger | (message: string, options?) => void | Error toast | | toast.warning | (message: string, options?) => void | Warning toast | | toast.info | (message: string, options?) => void | Info toast | | removeToast | (id: string) => void | Remove a specific toast |

ToastOptions

interface ToastOptions {
  title?: string    // Optional heading
  duration?: number // Auto-dismiss ms (default: 4000)
}

Usage

<script setup>
import { useToast } from 'nicklabs-ui'

const { toast } = useToast()

function save() {
  toast.success('Saved successfully!', { title: 'Done' })
}

function handleError() {
  toast.danger('Something went wrong.', { duration: 6000 })
}
</script>

useAlert

Display programmatic alert and confirm dialogs.

import { useAlert } from 'nicklabs-ui'

const { alert, confirm, clearAlerts } = useAlert()

Methods

| Method | Returns | Description | |--------|---------|-------------| | alert(message, options?) | Promise<void> | Show an alert dialog | | alert.success(message, options?) | Promise<void> | Success alert | | alert.warning(message, options?) | Promise<void> | Warning alert | | alert.danger(message, options?) | Promise<void> | Danger alert | | alert.info(message, options?) | Promise<void> | Info alert | | confirm(message, options?) | Promise<boolean> | Show a confirm dialog | | clearAlerts() | void | Dismiss all alerts |

AlertOptions

interface AlertOptions {
  title?: string
  confirmText?: string
  cancelText?: string   // confirm() only
}

Usage

<script setup>
import { useAlert } from 'nicklabs-ui'

const { alert, confirm } = useAlert()

async function deleteUser(id: number) {
  const confirmed = await confirm(
    'This action cannot be undone.',
    { title: 'Delete user?', confirmText: 'Delete', cancelText: 'Cancel' }
  )

  if (confirmed) {
    await api.deleteUser(id)
    await alert.success('User deleted.')
  }
}
</script>

useDisclosure

Simple boolean state management for modals and drawers.

import { useDisclosure } from 'nicklabs-ui'

// Array destructuring
const [isOpen, open, close] = useDisclosure()

Usage

<template>
  <NButton @click="open">Open Modal</NButton>
  <NModal v-model:show="isOpen" title="My Modal">
    <p>Content</p>
    <template #footer>
      <NButton @click="close">Close</NButton>
    </template>
  </NModal>
</template>

<script setup>
import { NModal, NButton, useDisclosure } from 'nicklabs-ui'

const [isOpen, open, close] = useDisclosure()
</script>

useBreadcrumb

Manage breadcrumb navigation state. NBreadcrumb reads from this composable automatically.

import { useBreadcrumb } from 'nicklabs-ui'

const {
  breadcrumbs,
  setLabelResolver,
  setBreadcrumbSuffix,
  clearSuffix,
  navigate,
} = useBreadcrumb()

Methods

| Method | Signature | Description | |--------|-----------|-------------| | setLabelResolver | (fn: (route) => string) => void | Custom function to extract label from a route record | | setBreadcrumbSuffix | (fn: (label: string) => string) => void | Transform the last breadcrumb label (e.g. append a record name) | | clearSuffix | () => void | Reset the suffix transform | | navigate | (path: string) => void | Navigate to a breadcrumb path |

BreadcrumbItem

interface BreadcrumbItem {
  label: string
  path?: string  // undefined for the last (active) crumb
}

Setup

Pass router when installing the plugin (see Setup). The label is read from route.meta.breadcrumb by default — no additional configuration needed.

Route meta

const routes = [
  {
    path: '/users',
    component: UserList,
    meta: { breadcrumb: 'Users' },
    children: [
      {
        path: ':id',
        component: UserDetail,
        meta: { breadcrumb: 'Detail' },
      },
    ],
  },
]

Dynamic suffix example

<script setup>
import { onMounted, onUnmounted } from 'vue'
import { useBreadcrumb } from 'nicklabs-ui'

const { setBreadcrumbSuffix, clearSuffix } = useBreadcrumb()

// Show user name as the last breadcrumb label
onMounted(() => setBreadcrumbSuffix(() => user.value.name))
onUnmounted(() => clearSuffix())
</script>

useSidebarManager

Manage sidebar open/close state, expandable menu groups (persisted to localStorage), and active state logic.

import { useSidebarManager } from 'nicklabs-ui'

const {
  isMenuExpanded,
  toggleMenu,
  isSidebarExpanded,
  toggleSidebar,
  setMenuActiveResolver,
  setItemActiveResolver,
} = useSidebarManager(sidebarId?)

Parameters

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | sidebarId | string | "default" | Unique ID for localStorage key |

Returns

| Property | Type | Description | |----------|------|-------------| | isMenuExpanded | (menuTitle: string) => boolean | Check if a menu group is expanded | | toggleMenu | (menuTitle: string) => void | Toggle a menu group | | isSidebarExpanded | () => boolean | Check if sidebar is open | | toggleSidebar | () => void | Toggle sidebar open/close | | setMenuActiveResolver | (fn: (menu: Menu, currentPath: string) => boolean) => void | Override the active logic for a top-level menu group | | setItemActiveResolver | (fn: (childMenu: MenuChild, currentPath: string) => boolean) => void | Override the active logic for individual menu items |

Custom active state

By default, NSidebar marks a menu item active when its route exactly matches currentPath. Use the resolver setters to provide custom logic — for example, prefix matching for nested routes:

import { useSidebarManager } from 'nicklabs-ui'
import type { MenuChild } from 'nicklabs-ui'

const { setItemActiveResolver } = useSidebarManager()

// Highlight the item whenever the current path starts with its route
setItemActiveResolver((childMenu: MenuChild, currentPath: string) => {
  return currentPath.startsWith(childMenu.route)
})

Call the setters once at app initialisation (e.g. main.ts or App.vue) — the setting is global and persists for the lifetime of the page.


useRouteModal

Open and close route-based modals using Vue Router's nested routes.

import { useRouteModal } from 'nicklabs-ui'

const { isOpen, open, close, params } = useRouteModal({
  routeName: 'user-detail',
  parentRouteName: 'users',   // optional fallback when no history
})

Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | routeName | string | Yes | Named route that represents the open modal state | | parentRouteName | string | No | Fallback route to navigate to on close when there is no browser history |

Returns

| Property | Type | Description | |----------|------|-------------| | isOpen | ComputedRef<boolean> | true when the named route is in the matched stack | | open | (params?) => void | Navigate to the modal route | | close | () => void | Navigate back (uses router.back() or parent route) | | params | ComputedRef<RouteParams> | Current route params |

Usage

// router/index.ts
const routes = [
  {
    path: '/users',
    name: 'users',
    component: UserList,
    children: [
      {
        path: ':id',
        name: 'user-detail',
        component: UserDetail,
      },
    ],
  },
]
<template>
  <NButton @click="open({ id: user.id })">View</NButton>

  <NModal v-model:show="isOpen" title="User Detail" @close="close">
    <p>ID: {{ params.id }}</p>
  </NModal>
</template>

<script setup>
import { useRouteModal } from 'nicklabs-ui'
import { NModal, NButton } from 'nicklabs-ui'

const { isOpen, open, close, params } = useRouteModal({
  routeName: 'user-detail',
  parentRouteName: 'users',
})
</script>

Type Reference

// Common
interface OptionItem {
  label: string
  value: any
}

type NSize = 'sm' | 'md' | 'lg'

// Table
interface NTableColumn {
  key: string
  label: string
  sortable?: boolean
}

type NTableSortOrder = 'asc' | 'desc' | null

interface NTableSortState {
  key: string
  order: NTableSortOrder
}

// Layout
type MenuChild = {
  icon: string
  title: string
  route: string
}

type Menu = {
  icon: string
  title: string
  children?: MenuChild[]
}

// Breadcrumb
interface BreadcrumbItem {
  label: string
  path?: string
}

// Sizes / Variants
type NButtonVariant = 'none' | 'solid' | 'outline' | 'ghost' | 'mute'
type NButtonIntent = 'none' | 'primary' | 'error' | 'success' | 'warning' | 'info'
type NButtonRadiusSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'
type NButtonSize = 'sm' | 'md' | 'lg'
type NTagIntent = 'none' | 'primary' | 'success' | 'warning' | 'error' | 'info'
type NTagVariant = 'solid' | 'light' | 'outline'
type PaddingSize = 'none' | 'sm' | 'md' | 'lg'
type RadiusSize = 'none' | 'sm' | 'md' | 'lg' | 'xl'

CSS Variables

The library reads these CSS custom properties from the host application. Override them to theme the components.

:root {
  /* Colors */
  --primary-color: #4f6ef7;
  --primary-light: #7b93fa;
  --primary-dark: #3554e1;
  --success-color: #22c55e;
  --error-color: #ef4444;
  --warning-color: #f59e0b;
  --info-color: #3b82f6;

  /* Text */
  --text-main: #1e293b;
  --text-secondary: #475569;
  --text-muted: #94a3b8;
  --text-inverse: #ffffff;

  /* Backgrounds */
  --bg-body: #f1f5f9;
  --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

  /* Surfaces (glassmorphism) */
  --surface-solid: #ffffff;
  --surface-glass: rgba(255, 255, 255, 0.7);
  --surface-glass-border: rgba(255, 255, 255, 0.3);
  --surface-glass-hover: rgba(255, 255, 255, 0.85);

  /* Border */
  --border-color: #e2e8f0;

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;

  /* Layout */
  --header-height: 56px;
  --sidebar-width: 60px;
  --sidebar-width-open: 240px;
  --sidebar-logo-height: 48px;

  /* Inputs */
  --input-bg: #ffffff;
  --input-disabled-bg: #f8fafc;
  --input-disabled-text: #94a3b8;

  /* Transitions */
  --transition-fast: 0.15s ease;
  --transition-normal: 0.25s ease;
}

The variables.css export from nicklabs-ui provides a default set of these variables. Import it as a starting point and override as needed in your own stylesheet.