Skip to content

UI Components ​

Component library based on shadcn-vue with custom extensions.

Overview ​

The project uses shadcn-vue - a collection of re-usable components built with Reka UI and styled with Tailwind CSS.

Component Prefix

All shadcn-vue components are prefixed with Ui (e.g., UiButton, UiDialog, UiInput) and are auto-imported.

Installation ​

Adding New Components ​

When you need a component that's not yet installed:

bash
# From the frontend directory
npx shadcn-vue@latest add <component-name>

# Examples:
npx shadcn-vue@latest add button
npx shadcn-vue@latest add dialog
npx shadcn-vue@latest add select

This installs the component in app/components/ui/<component-name>/ with project styling applied.

Available Components ​

Check app/components/ui/ for installed components. For usage documentation, see the shadcn-vue docs.

Project-Specific Usage ​

Form Integration ​

Components integrate with VeeValidate using v-bind="componentField":

vue
<template>
  <UiFormField name="email" v-slot="{ componentField }">
    <UiFormGroup label="Email" required>
      <UiInput v-bind="componentField" type="email" />
    </UiFormGroup>
  </UiFormField>
</template>

See Forms & Validation → for complete patterns.

Custom Components ​

RTL-Aware Chevrons ​

Use these instead of regular Lucide chevrons for automatic RTL support:

vue
<template>
  <!-- Automatically flips in RTL -->
  <ChevronStart :size="20" />  <!-- Left in LTR, Right in RTL -->
  <ChevronEnd :size="20" />    <!-- Right in LTR, Left in RTL -->
  <ChevronsStart />
  <ChevronsEnd />
</template>

See RTL Support → for details.

Toast Notifications ​

Use vue-sonner for toasts:

vue
<template>
  <UiButton @click="showToast">Show Toast</UiButton>
</template>

<script setup>
const { toast } = useToast()

const showToast = () => {
  toast.success('Operation successful!')
  toast.error('Something went wrong')
  toast.info('Information')
  toast.warning('Warning')
}
</script>

TIP

<UiToaster /> is already in app.vue - no setup needed!

Confirmation Dialogs ​

Use useConfirm for quick confirmations:

vue
<template>
  <UiButton @click="handleDelete">Delete</UiButton>
</template>

<script setup>
const { confirm } = useConfirm()

const handleDelete = async () => {
  const confirmed = await confirm({
    title: 'Delete Employee',
    description: 'Are you sure? This action cannot be undone.',
    confirmText: 'Delete',
    cancelText: 'Cancel'
  })
  
  if (confirmed) {
    // Perform deletion
  }
}
</script>

See useConfirm documentation for more options.

The cn() Utility ​

Use cn() from @/lib/utils to merge classes:

vue
<script setup>
import { cn } from '@/lib/utils'

const buttonClass = computed(() => cn(
  'px-4 py-2 rounded-md',
  props.variant === 'destructive' && 'bg-red-500',
  props.disabled && 'opacity-50'
))
</script>

What it does:

  • Merges class strings intelligently
  • Resolves conflicts (last class wins)
  • Works with conditional classes

Component Customization ​

Add layout/spacing classes via the class prop:

vue
<template>
  <!-- Add layout classes only -->
  <UiCard class="max-w-2xl mx-auto">
    <UiCardHeader>
      <UiCardTitle class="text-center">Title</UiCardTitle>
    </UiCardHeader>
  </UiCard>
  
  <UiButton class="w-full">Full Width</UiButton>
</template>

Don't Duplicate Styles

Components have base styling. Only add:

  • Layout: w-full, max-w-*, mx-auto
  • Spacing: mt-4, space-y-2
  • Responsive: md:grid-cols-2

Don't add existing styles like rounded-md, border, shadow.

Responsive Patterns ​

Components are mobile-first and responsive:

vue
<template>
  <!-- Responsive grid -->
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    <UiCard v-for="item in items" :key="item.id">
      <!-- Content -->
    </UiCard>
  </div>
  
  <!-- Sheet for mobile, Popover for desktop -->
  <UiSheet v-if="isMobile" v-model:open="isOpen">
    <!-- Mobile -->
  </UiSheet>
  <UiPopover v-else v-model:open="isOpen">
    <!-- Desktop -->
  </UiPopover>
</template>

<script setup>
import { useMediaQuery } from '@vueuse/core'

const isMobile = useMediaQuery('(max-width: 768px)')
</script>

See Styling & Theming → for responsive patterns.

Best Practices ​

Do's âś…

  • Check app/components/ui/ before installing
  • Use component variants (variant, size)
  • Leverage cn() for conditional styling
  • Keep customization minimal
  • See shadcn-vue docs for usage

Don'ts ❌

  • Don't create custom components that exist in shadcn-vue
  • Don't duplicate base component styles
  • Don't use inline styles
  • Don't forget RTL support for directional UI

Resources ​


Related: Forms & Validation → | Styling & Theming → | RTL Support → | Data Tables →