Appearance
Useful Composables ​
Common composables for everyday development tasks.
useConfirm ​
Prompt users for confirmation with dialogs.
vue
<template>
<UiButton @click="handleDelete">Delete</UiButton>
</template>
<script setup>
const { confirm } = useConfirm()
const handleDelete = async () => {
const confirmed = await confirm('Delete this item?', {
description: 'This action cannot be undone.',
variant: 'destructive',
confirmButtonText: 'Delete'
})
if (confirmed) {
// User clicked confirm
await deleteItem()
}
}
</script>Variants ​
default- Standard confirmation (blue)destructive- Dangerous actions (red)warning- Caution required (yellow)info- Informationalsuccess- Positive confirmation (green)
With i18n ​
vue
<script setup>
const { t } = useI18n()
const { confirm } = useConfirm()
const handleDelete = async () => {
const confirmed = await confirm(
() => t('employees.deleteTitle'),
{
description: () => t('employees.deleteDescription'),
confirmButtonText: () => t('common.actions.delete'),
variant: 'destructive'
}
)
}
</script>Reactive Translations
Use getter functions () => t('key') to make dialog text reactive to language changes.
With Async Handler ​
Manage edit mode state for forms and cards.
vue
<template>
<UiCard>
<UiCardHeader>
<UiCardTitle>Employee Details</UiCardTitle>
<!-- Edit/Save/Cancel buttons -->
<div v-if="isEditing" class="flex gap-2">
<UiButton @click="cancel">Cancel</UiButton>
<UiButton @click="handleSave" :disabled="!isDirty">Save</UiButton>
</div>
<UiButton v-else @click="startEdit">Edit</UiButton>
</UiCardHeader>
<UiCardContent>
<!-- View mode -->
<div v-if="!isEditing">
<p>{{ employee.name }}</p>
<p>{{ employee.email }}</p>
</div>
<!-- Edit mode -->
<div v-else>
<UiInput v-model="editableValue.name" />
<UiInput v-model="editableValue.email" />
</div>
</UiCardContent>
</UiCard>
</template>
<script setup>
const props = defineProps<{
employee: Employee
}>()
const emit = defineEmits<{
'save': [Employee]
}>()
// Set up edit mode
const {
isEditing, // Boolean: currently editing?
editableValue, // Ref: working copy of data
isDirty, // Boolean: has data changed?
startEdit, // Function: enter edit mode
cancel, // Function: cancel and revert changes
save // Function: save and exit edit mode
} = useEdit(() => props.employee)
const handleSave = () => {
const saved = save()
if (saved) {
emit('save', saved)
}
}
</script>What useEdit provides:
isEditing- Boolean indicating edit modeeditableValue- Ref with working copy of dataisDirty- Boolean indicating if data changedstartEdit()- Enter edit modecancel()- Cancel and revert changessave()- Save and exit, returns saved datareset()- Reset to initial value
Clean Edit Flow
useEdit handles cloning, change detection, and state management automatically!
useRouteLeaveGuard ​
Prevent navigation when there are unsaved changes.
vue
<script setup>
const formData = ref({ name: '', email: '' })
const isDirty = ref(false)
// Block navigation if form has unsaved changes
useRouteLeaveGuard(isDirty)
const handleInput = () => {
isDirty.value = true
}
const handleSave = async () => {
await saveData()
isDirty.value = false // Allow navigation after save
}
</script>With i18n ​
Use getter functions for reactive translations:
vue
<script setup>
const { t } = useI18n()
const hasUnsavedChanges = ref(false)
useRouteLeaveGuard(hasUnsavedChanges, {
confirmTitle: () => t('forms.unsavedChanges'),
confirmDescription: () => t('forms.unsavedWarning'),
confirmButtonText: () => t('common.actions.discard'),
cancelButtonText: () => t('common.actions.keepEditing'),
confirmVariant: 'warning'
})
</script>Options ​
confirmTitle- Dialog title (string, ref, or getter function)confirmDescription- Dialog descriptionconfirmVariant-'default'|'destructive'|'warning'|'info'|'success'confirmButtonText- Confirm button textcancelButtonText- Cancel button textguardRouteUpdate- Also guard when route params change (default: false)
Default translations (if no custom text provided):
- Title:
common.routeLeave.title- "You have unsaved changes" - Description:
common.routeLeave.description- "Are you sure you want to leave? Your changes will be lost." - Confirm:
common.routeLeave.leave- "Leave" - Cancel:
common.routeLeave.stay- "Stay" - Variant:
'warning'
When to Use
Perfect for forms with unsaved changes, multi-step wizards, or any scenario where leaving the page would lose user data.
useRtl ​
Access RTL/LTR direction information.
vue
<script setup>
const { dir, resolvedDir, isRtl } = useRtl()
// dir: 'ltr' | 'rtl' | 'auto'
// resolvedDir: 'ltr' | 'rtl' (never 'auto')
// isRtl: boolean
</script>
<template>
<!-- Conditional rendering -->
<ChevronLeft v-if="!isRtl" />
<ChevronRight v-else />
<!-- Conditional classes -->
<div :class="isRtl && 'scale-x-[-1]'">
Icon
</div>
</template>See RTL Support → for complete guide.
useAuthUserStore ​
Access current user and organization context.
vue
<script setup>
const {
user, // Current user
currentOrganization, // Current org context
currentEmployee, // Current employee context
currentAccess, // 'organization' | 'employee' | 'none'
organizations, // Available organizations
employees, // Available employee accounts
hasCompanyAccess, // Boolean
hasEmployeeAccess // Boolean
} = useAuthUserStore()
</script>
<template>
<div>
<p>Welcome, {{ user?.name }}</p>
<p v-if="currentOrganization">
Organization: {{ currentOrganization.name }}
</p>
</div>
</template>useOrganizationDataStore ​
Access cached organization data (departments, positions).
vue
<script setup>
const {
departments, // Ref<Department[]>
positions, // Ref<Position[]>
getDepartmentName, // (id) => string
getPositionName, // (id) => string
addDepartment, // (dept) => Promise<Department>
addPosition // (pos) => Promise<Position>
} = useOrganizationDataStore()
// Data is automatically lazy-loaded and cached
</script>
<template>
<UiSelect v-model="selectedDept">
<UiSelectTrigger>
<UiSelectValue placeholder="Select department" />
</UiSelectTrigger>
<UiSelectContent>
<UiSelectItem
v-for="dept in departments"
:key="dept.id"
:value="dept.id"
>
{{ dept.name }}
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</template>Automatic Caching
Data is cached using useState and persists across navigation. The store automatically fetches on first use.
Input Masking ​
For formatting inputs (phone numbers, dates, SSNs), see Input Masking →.
Best Practices ​
Do's âś…
- Use
useConfirmfor all user confirmations - Use
useEditfor consistent edit mode behavior - Use
useRouteLeaveGuardto prevent data loss on navigation - Use
useOrganizationDataStorefor cached org data - Use getter functions with
useConfirmfor reactive i18n
Don'ts ❌
- Don't create custom confirmation dialogs (use
useConfirm) - Don't manually manage edit state (use
useEdit) - Don't let users lose unsaved data (use
useRouteLeaveGuard) - Don't fetch departments/positions directly (use store)
Quick Reference ​
vue
<script setup>
// Confirmation
const { confirm } = useConfirm()
await confirm('Delete?', { variant: 'destructive' })
// Edit mode
const { isEditing, editableValue, isDirty, startEdit, save, cancel } = useEdit(() => data)
// Route leave guard
useRouteLeaveGuard(hasUnsavedChanges, { confirmVariant: 'warning' })
// RTL
const { isRtl } = useRtl()
// Auth
const { user, currentOrganization } = useAuthUserStore()
// Org data
const { departments, positions } = useOrganizationDataStore()
</script>Related: UI Components → | API Calls → | Forms → | Input Masking →