Appearance
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 selectThis 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 ​
- shadcn-vue Documentation - Component usage and examples
- Reka UI - Headless component primitives
- Local Examples - Browse
app/pages/for real usage
Related: Forms & Validation → | Styling & Theming → | RTL Support → | Data Tables →