Skip to content

UI Component Library — Usage Guide ​

Welcome to the Design System UI Component Library. Every component in this directory is auto-imported globally by Nuxt 4 and utilizes design tokens defined in our CSS custom properties (no hardcoded styling values).


Table of Contents ​

  1. UiSpinner
  2. UiButton
  3. UiInput
  4. UiBadge
  5. UiCard
  6. UiAlert
  7. UiSelect
  8. UiTextarea
  9. UiTable
  10. UiPagination
  11. UiModal
  12. UiDrawer
  13. UiPageState

UiSpinner ​

A flexible loading spinner used as the default indicator throughout the application.

Props ​

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'The size of the spinner (sm: 16px, md: 24px, lg: 40px)
classstringundefinedCustom CSS classes to apply

Example ​

vue
<template>
  <div class="flex items-center gap-2">
    <UiSpinner size="sm" />
    <span>Loading...</span>
  </div>
</template>

UiButton ​

A robust button component utilizing reka-ui Primitive. Supports variant styling, size options, and a native loading spinner state.

Props ​

PropTypeDefaultDescription
variant'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link''default'Visual styling variant
size'default' | 'sm' | 'lg' | 'icon''default'Physical size options
loadingbooleanfalseSets loading state, disables action, displays spinner, auto disable and spinner during loading
asstring'button'Underlying HTML element to render
asChildbooleanfalsePass-through rendering pattern

Example ​

vue
<template>
  <!-- Standard Button -->
  <UiButton variant="primary" @click="handleClick">
    Submit Entry
  </UiButton>

  <!-- Loading State Button -->
  <UiButton variant="secondary" :loading="isLoading">
    Save Changes
  </UiButton>
</template>

UiInput ​

Fully accessible form input field component featuring built-in state styling for errors, custom clearable actions, labels, and support text.

Props ​

PropTypeDefaultDescription
modelValuestring | numberundefinedBinding value
defaultValuestring | numberundefinedInitial default value
typestring'text'HTML input type attribute
placeholderstringundefinedInput placeholder value
disabledbooleanfalseDisables user interaction
errorstringundefinedDisplays error message and triggers invalid borders
clearablebooleanfalseDisplays a clear button when text exists
idstringundefinedAccessibility ID

Slots ​

  • label: Custom label node
  • message: Custom description message block

Emits ​

  • update:modelValue: Fired on value updates
  • clear: Fired when the clear button is clicked

Example ​

vue
<script setup lang="ts">
const name = ref('')
const inputError = ref('')
</script>

<template>
  <UiInput
    v-model="name"
    id="username"
    placeholder="Enter Username"
    :error="inputError"
    clearable
  >
    <template #label>Username</template>
    <template #message>Choose a unique identifier.</template>
  </UiInput>
</template>

UiBadge ​

A chip status element mapped cleanly to Tailwind status color tokens.

Props ​

PropTypeDefaultDescription
status'active' | 'pending' | 'cancelled' | 'overdue' | 'paid' | 'rejected' | 'non_payer''active'Maps the badge to the corresponding design token palette

Example ​

vue
<template>
  <UiBadge status="paid">
    Transaction Approved
  </UiBadge>
</template>

UiCard ​

A container component for grouping content with optional header and footer panels.

Slots ​

  • default: Core card body layout
  • header: Header title panel
  • footer: Action buttons/footer panel

Example ​

vue
<template>
  <UiCard class="max-w-md">
    <template #header>
      <h3 class="text-lg font-bold">Profile Card</h3>
    </template>
    <p>User account configuration and preferences go here.</p>
    <template #footer>
      <UiButton variant="outline">Cancel</UiButton>
    </template>
  </UiCard>
</template>

UiAlert ​

Status warning blocks for context alerts, available in multiple status levels.

Props ​

PropTypeDefaultDescription
variant'info' | 'success' | 'warning' | 'danger''info'Alert severity configuration
dismissiblebooleanfalseRenders close button if true

Emits ​

  • dismiss: Emitted when the user closes the alert

Example ​

vue
<template>
  <UiAlert variant="success" dismissible @dismiss="onAlertClose">
    Profile successfully updated.
  </UiAlert>
</template>

UiSelect ​

Accessible custom selector component based on reka-ui.

Props ​

PropTypeDefaultDescription
modelValuestringundefinedCurrent chosen value
optionsSelectOption[][]List of items: { label: string, value: string, disabled?: boolean }
placeholderstring'Select an option'Placeholder content
errorstringundefinedCustom validation error string
disabledbooleanfalseDisables selector interaction
idstringundefinedIdentifier attribute

Example ​

vue
<script setup lang="ts">
const selection = ref('')
const items = [
  { label: 'Role Developer', value: 'developer' },
  { label: 'Role Admin', value: 'admin' }
]
</script>

<template>
  <UiSelect
    v-model="selection"
    :options="items"
    placeholder="Choose your workspace role"
  >
    <template #label>User Role</template>
  </UiSelect>
</template>

UiTextarea ​

A responsive multi-line input field, supporting standard features alongside optional auto-height adjustments.

Props ​

PropTypeDefaultDescription
modelValuestringundefinedBinding value
defaultValuestringundefinedBaseline text
errorstringundefinedError feedback string
autoResizebooleanfalseAuto-sizes height to prevent scroll bars if true
rowsnumber3Base height (in rows)

Example ​

vue
<script setup lang="ts">
const bio = ref('')
</script>

<template>
  <UiTextarea
    v-model="bio"
    placeholder="Write your bio..."
    auto-resize
  >
    <template #label>Biography</template>
  </UiTextarea>
</template>

UiTable ​

A complete visual grid component featuring integrated dynamic cells, responsive loading feedback, empty status layout, and column sorting flags.

Props ​

PropTypeDefaultDescription
columnsTableColumn[](required)Array of structure elements: { key: string, label: string, sortable?: boolean, class?: string }
rowsRecord<string, unknown>[](required)Array of data structures
sortKeystringundefinedColumn key representing active sorting
sortDir'asc' | 'desc'undefinedSorting direction
loadingbooleanfalseShows a overlay loader when true

Slots ​

  • cell-[key]: Custom formatter cell for the column matching the specified key. Injected with { row, value }.
  • loading: Custom loader spinner structure.
  • empty: Custom empty grid feedback node.

Emits ​

  • sort: Emitted when a sortable column header cell is triggered. Emits the column key.

Example ​

vue
<script setup lang="ts">
const columns = [
  { key: 'title', label: 'Title', sortable: true },
  { key: 'status', label: 'Status' }
]
const rows = [
  { title: 'Project Alfa', status: 'active' },
  { title: 'Project Beta', status: 'pending' }
]
</script>

<template>
  <UiTable :columns="columns" :rows="rows">
    <template #cell-status="{ value }">
      <UiBadge :status="value === 'active' ? 'active' : 'pending'">
        {{ value.toUpperCase() }}
      </UiBadge>
    </template>
  </UiTable>
</template>

Server-Side (API) Sorting Example ​

For production applications with paginated backend databases, you should handle sorting via API requests. When a user clicks a sortable column header, UiTable emits the @sort event with the clicked column key. You can then trigger an asynchronous network request (or simulate it with a fake API fetch) as shown in the example below:

vue
<script setup lang="ts">
import { ref, watch } from 'vue'
import type { TableColumn } from '~/components/ui/table'

const columns: TableColumn[] = [
  { key: 'name', label: 'Name', sortable: true },
  { key: 'role', label: 'Role' },
  { key: 'amount', label: 'Amount', sortable: true, class: 'text-right' },
]

// Bindings for active sorting state
const sortKey = ref('name')
const sortDir = ref<'asc' | 'desc'>('asc')
const isLoading = ref(false)
const items = ref<any[]>([])

// Simulated backend API response with network latency
async function fetchItemsFromApi(key: string, dir: 'asc' | 'desc') {
  isLoading.value = true
  try {
    // 1. Simulate 500ms network round-trip time
    await new Promise(resolve => setTimeout(resolve, 500))

    // 2. Mock calling your Nuxt repository / fetch client
    // e.g. const res = await $kfFetch('/api/users', { query: { sortBy: key, order: dir } })
    const mockDb = [
      { name: 'Htin Linn', role: 'Admin', amount: 250 },
      { name: 'Yimon Khaing', role: 'Member', amount: 120 },
      { name: 'Kyaw Zin', role: 'Viewer', amount: 75 },
    ]

    // Simulate database order handling on the mock "server"
    mockDb.sort((a, b) => {
      const valA = a[key as keyof typeof a]
      const valB = b[key as keyof typeof b]
      if (typeof valA === 'number' && typeof valB === 'number') {
        return dir === 'asc' ? valA - valB : valB - valA
      }
      return dir === 'asc' 
        ? String(valA).localeCompare(String(valB)) 
        : String(valB).localeCompare(String(valA))
    })

    items.value = mockDb
  } catch (err) {
    console.error('Error fetching data from API:', err)
  } finally {
    isLoading.value = false
  }
}

// 3. Trigger sort direction swap or switch sortKey on @sort emit
function onSort(key: string) {
  if (sortKey.value === key) {
    sortDir.value = sortDir.value === 'asc' ? 'desc' : 'asc'
  } else {
    sortKey.value = key
    sortDir.value = 'asc'
  }
}

// 4. Watch sorting criteria changes to fetch fresh data from the server automatically
watch([sortKey, sortDir], () => {
  fetchItemsFromApi(sortKey.value, sortDir.value)
}, { immediate: true })
</script>

<template>
  <div class="space-y-4">
    <div class="flex items-center justify-between">
      <h3 class="text-sm font-semibold text-slate-500 uppercase">
        Server-Side Sorted Table
      </h3>
      <span v-if="isLoading" class="text-xs text-indigo-600 animate-pulse">
        Fetching from API...
      </span>
    </div>

    <UiTable
      :columns="columns"
      :rows="items"
      :sort-key="sortKey"
      :sort-dir="sortDir"
      :loading="isLoading"
      @sort="onSort"
    >
      <!-- Format cell contents -->
      <template #cell-amount="{ value }">
        <span class="font-semibold font-mono block text-right">
          ${{ value.toFixed(2) }}
        </span>
      </template>
    </UiTable>
  </div>
</template>

UiPagination ​

A cursor-based navigation component for records lists, showing standard next and prev hooks.

Props ​

PropTypeDefaultDescription
prevCursorstring | nullnullPrevious pagination cursor value. Renders button disabled if null.
nextCursorstring | nullnullNext pagination cursor value. Renders button disabled if null.

Emits ​

  • change: Emits target cursor value string on user click action.

Example ​

vue
<template>
  <UiPagination
    :prev-cursor="prevVal"
    :next-cursor="nextVal"
    @change="fetchPage"
  />
</template>

UiModal ​

A clean accessible popup container built around reka-ui Dialog, utilizing subtle backdrops and zoom transitions.

Props ​

PropTypeDefaultDescription
openbooleanfalseVisibility state control
persistentbooleanfalseDisables close on Escape / outside click
titlestringundefinedStandard header title
descriptionstringundefinedStandard support description

Slots ​

  • default: Core modal contents
  • header: Custom title / subtitle panel overrides
  • footer: Custom control buttons row

Emits ​

  • close: Emitted when the modal closes (via close click, Escape, or backdrop click)

Example ​

vue
<template>
  <UiModal :open="isOpen" title="Confirm Action" @close="isOpen = false">
    <p>Are you sure you want to proceed with deletion?</p>
    <template #footer>
      <UiButton variant="ghost" @click="isOpen = false">Cancel</UiButton>
      <UiButton variant="destructive">Confirm</UiButton>
    </template>
  </UiModal>
</template>

UiDrawer ​

A sliding drawer panel component suitable for secondary configuration blocks or details sheets.

Props ​

Shares identical prop list and attributes as UiModal.

Example ​

vue
<template>
  <UiDrawer :open="isConfigOpen" title="Settings Panel" @close="isConfigOpen = false">
    <div class="space-y-4">
      <h4 class="text-sm font-semibold">User details</h4>
      <p>Modify specific elements inside this sidebar.</p>
    </div>
  </UiDrawer>
</template>

UiPageState ​

A page loading state orchestrator designed to wrap full viewport sections, responding gracefully with customized spinners, error message displays, or empty notifications.

Props ​

PropTypeDefaultDescription
status'pending' | 'error' | 'empty' | 'success'(required)Current lifecycle status
errorMessagestring'Something went wrong. Please try again.'Message displayed under error status

Slots ​

  • default: Content to mount when status is 'success'
  • empty: Custom empty status panel nodes

Emits ​

  • retry: Emitted when the error state 'Try again' button is clicked

Example ​

vue
<script setup lang="ts">
const status = ref<'pending' | 'error' | 'empty' | 'success'>('pending')
</script>

<template>
  <UiPageState :status="status" @retry="loadData">
    <div>
      <h3>Page Title Loaded</h3>
      <p>Data loads and displays successfully.</p>
    </div>
  </UiPageState>
</template>

KPZ frontend · Team documentation