38 KiB
Vuexy Component Registry
Reference document for all frontend work. Consult BEFORE writing any new component.
Generated from Vuexy template v10.11.1 (resources/vuexy-admin-v10.11.1/vue-version/) and Crewli codebase scan.
1. @core Components
Source: resources/vuexy-admin-v10.11.1/vue-version/typescript-version/full-version/src/@core/components/
Copied into: apps/app/src/@core/components/ and apps/portal/src/@core/components/
Form Element Wrappers (@core/components/app-form-elements/)
These wrap Vuetify form components with a separate label row for consistent styling. Use these instead of raw Vuetify form components in all forms.
| Component | Purpose | Key Props | When to Use |
|---|---|---|---|
| AppTextField | VTextField wrapper with separate label | All VTextField props via $attrs + label |
Text input fields |
| AppTextarea | VTextarea wrapper with separate label | All VTextarea props via $attrs + label |
Multi-line text input |
| AppSelect | VSelect wrapper with separate label | All VSelect props via $attrs + label |
Dropdown select fields |
| AppAutocomplete | VAutocomplete wrapper with separate label | All VAutocomplete props via $attrs + label |
Searchable dropdowns |
| AppCombobox | VCombobox wrapper with separate label | All VCombobox props via $attrs + label |
Free-text + dropdown combo |
| AppDateTimePicker | Flatpickr-based date/time picker with Vuetify styling | modelValue, placeholder, autofocus, counter, prefix, suffix, all VInput/VField props |
Date and time selection |
All wrappers forward all slots and events from the underlying Vuetify component.
Custom Form Controls (@core/components/app-form-elements/)
Styled card-based selection controls with title, description, and optional media.
| Component | Purpose | Key Props | Emits |
|---|---|---|---|
| CustomCheckboxes | Styled checkboxes with title + description | selectedCheckbox: string[], checkboxContent: CustomInputContent[], gridColumn? |
update:selectedCheckbox |
| CustomCheckboxesWithIcon | Checkboxes with icon display | Same as above (content includes icon) |
update:selectedCheckbox |
| CustomCheckboxesWithImage | Checkboxes with image backgrounds | selectedCheckbox: string[], checkboxContent: { bgImage, value, label? }[] |
update:selectedCheckbox |
| CustomRadios | Styled radio buttons with title + description | selectedRadio: string, radioContent: CustomInputContent[], gridColumn? |
update:selectedRadio |
| CustomRadiosWithIcon | Radio buttons with icon display | Same as above (content includes icon) |
update:selectedRadio |
| CustomRadiosWithImage | Radio buttons with image backgrounds | selectedRadio: string, radioContent: { bgImage, value, label? }[] |
update:selectedRadio |
Card Components (@core/components/cards/)
| Component | Purpose | Key Props | When to Use |
|---|---|---|---|
| CardStatisticsHorizontal | Stat card — icon right, text left | title, icon, stats, color? (default: primary) |
Dashboard KPI cards (horizontal layout) |
| CardStatisticsVertical | Stat card with embedded ApexChart | title, icon, stats, height, series, chartOptions, color? |
Stat cards with trend charts |
| CardStatisticsVerticalSimple | Simple stat card — icon + value | title, icon, stats, color? |
Simple stat display without charts |
| AppCardActions | Card with collapse/refresh/remove action buttons | collapsed?, noActions?, actionCollapsed?, actionRefresh?, actionRemove?, loading?, title? |
Cards needing user-togglable actions |
| AppCardCode | Code display with syntax highlighting + TS/JS toggle | title, code: Record<'ts'|'js', string>, codeLanguage?, noPadding? |
Documentation / code examples |
Utility Components (@core/components/)
| Component | Purpose | Key Props / Events | When to Use |
|---|---|---|---|
| AppStepper | Multi-step wizard (horizontal/vertical) | items: Item[], currentStep?, direction?, iconSize?, isActiveStepValid?, align?; emits update:currentStep |
Multi-step forms, wizards, processes |
| AppDrawerHeaderSection | Drawer header with title + close button | title; emits cancel; slot: beforeClose |
Header section in side drawers |
| AppBarSearch | Full-screen search dialog (Ctrl+K / Cmd+K) | isDialogVisible, searchResults: T[], isLoading?; emits search, update:isDialogVisible; slots: suggestions, searchResult, noData, noDataSuggestion |
Global search in app bar |
| DialogCloseBtn | Styled close button (X icon) for dialogs | icon? (default: tabler-x), iconSize? (default: 20) |
Close button in dialog/modal headers |
| TablePagination | Table pagination with item count display | page, itemsPerPage, totalItems; emits update:page |
Custom pagination for non-VDataTable tables |
| MoreBtn | Three-dot context menu button | menuList?, itemProps?, iconSize?, class? |
More actions menu in cards/table rows |
| ScrollToTop | Fixed scroll-to-top button (appears after 200px scroll) | None | Add to layout for long pages |
| TiptapEditor | Rich text editor (Tiptap) with formatting toolbar | modelValue, placeholder?; emits update:modelValue |
Rich text editing (notes, descriptions) |
| ProductDescriptionEditor | Rich text editor variant with toolbar | Same as TiptapEditor | Product/content description editing |
| DropZone | Drag-and-drop file upload with image preview cards | None (internal state) | Image file uploads |
| TheCustomizer | Theme customizer drawer (primary color, mode, skin, layout, width, direction) | None (reads/writes config store) | App-level theme customization panel |
| ThemeSwitcher | Light/Dark/System theme switcher dropdown | themes: ThemeSwitcherTheme[] |
Theme switching in app bar |
| Notifications | Notification bell with dropdown panel | notifications, badgeProps?, location?; emits read, unread, remove, click:notification |
User notifications in app bar |
| Shortcuts | Grid menu of quick-access shortcut links | shortcuts: Shortcut[], togglerIcon? |
Quick navigation in app bar |
| I18n | Language/locale switcher menu | languages: I18nLanguage[], location? |
Language switching in app bar |
| CustomizerSection | Section wrapper for theme customizer | title, divider? (default: true) |
Organize customizer sections |
2. Layouts
@layouts System (@layouts/)
The layout system is a Vuexy plugin providing navigation infrastructure, state management, and responsive behavior.
Layout Components (@layouts/components/)
| Component | Purpose | Key Props / Slots |
|---|---|---|
| VerticalNavLayout | Complete vertical nav layout wrapper | Props: navItems, verticalNavAttrs; Slots: vertical-nav-header, before-vertical-nav-items, navbar (receives toggleVerticalOverlayNavActive), default (page content), footer |
| VerticalNav | Sidebar navigation with PerfectScrollbar | Props: tag, navItems, isOverlayNavActive, toggleIsOverlayNavActive; Slots: nav-header, before-nav-items, nav-items, after-nav-items |
| VerticalNavLink | Individual nav link with ACL support | Props: item: NavLink (title, icon, to/href, badgeContent, disable) |
| VerticalNavGroup | Expandable nav group with accordion behavior | Props: item: NavGroup (title, icon, children[]) |
| VerticalNavSectionTitle | Section divider/header in nav | Props: item: NavSectionTitle (heading) |
| HorizontalNavLayout | Complete horizontal nav layout wrapper | Props: navItems; Slots: navbar, default, footer |
| HorizontalNav | Horizontal navigation bar | Props: navItems |
| HorizontalNavLink | Horizontal nav link item | Props: item: NavLink, isSubItem |
| HorizontalNavGroup | Dropdown group for horizontal nav | Props: item: NavGroup, childrenAtEnd, isSubItem |
| HorizontalNavPopper | Floating UI dropdown for horizontal nav | Props: popperInlineEnd, tag, contentContainerTag, isRtl; Slots: default (trigger), content |
| TransitionExpand | Smooth height expand/collapse animation | Used by VerticalNavGroup |
| VNodeRenderer | Generic VNode rendering (TSX) | Props: nodes: VNode | VNode[] |
Layout Configuration (@layouts/config.ts)
| Setting | Default | Options |
|---|---|---|
| Content width | Boxed | ContentWidth.Fluid, ContentWidth.Boxed |
| Nav type | Vertical | AppContentLayoutNav.Vertical, AppContentLayoutNav.Horizontal |
| Overlay nav breakpoint | md (768px) | Any Vuetify breakpoint |
| Navbar | Sticky | NavbarType.Sticky, NavbarType.Static, NavbarType.Hidden |
| Footer | Static | FooterType.Sticky, FooterType.Static, FooterType.Hidden |
| Horizontal nav | Sticky | Same as NavbarType |
| Navbar blur | true | boolean |
| Vertical nav collapsed | false | boolean |
Layout Store (useLayoutConfigStore)
Pinia store managing all layout state. Values persisted via cookies:
navbarType,isNavbarBlurEnabled,isVerticalNavCollapsedappContentWidth,appContentLayoutNavhorizontalNavType,footerType,isAppRTL
Computed: _layoutClasses, isLessThanOverlayNavBreakpoint, isVerticalNavMini(isVerticalNavHovered)
ACL Integration (@layouts/plugins/casl.ts)
can(action, subject)— Check user ability (returns true if ACL disabled)canViewNavMenuGroup(item)— Determines if group renders based on children abilitiescanNavigate(to)— Route-level access control usingroute.meta.action/subject
App Layouts (apps/app/src/layouts/)
| Layout | File | Used In | Purpose |
|---|---|---|---|
| default | layouts/default.vue |
All authenticated pages | Switches between vertical/horizontal nav based on config store |
| blank | layouts/blank.vue |
Login, error pages | Minimal layout — no navigation |
default delegates to:
layouts/components/DefaultLayoutWithVerticalNav.vue— Vertical sidebar (default)layouts/components/DefaultLayoutWithHorizontalNav.vue— Horizontal top nav (alternative)
Navbar components (in layouts/components/):
UserProfile.vue— Avatar menu with logoutNavSearchBar.vue— Search with grouped suggestionsNavBarNotifications.vue— Notification bell + panelNavbarShortcuts.vue— Quick access buttonsNavbarThemeSwitcher.vue— Light/Dark/System switcherFooter.vue— Copyright footer
Portal Layouts (apps/portal/src/layouts/)
| Layout | File | Used In | Purpose |
|---|---|---|---|
| portal | layouts/portal.vue |
All authenticated portal pages | Custom navbar with two modes + mobile drawer |
| blank | layouts/blank.vue |
Login, registration, password reset | Minimal layout |
| default | layouts/default.vue |
Fallback (delegates to portal) | Wrapper |
Portal layout is a custom implementation — does NOT use @layouts VerticalNavLayout. Features:
- Platform mode (
navMode: 'platform'): Crewli logo + optional page title + UserAvatarMenu - Event mode (
navMode: 'event'): Org name + event name + back link - Mobile: VNavigationDrawer hamburger menu with user info + nav links + logout
- Desktop: UserAvatarMenu component in navbar
- Container max-width: 1440px, navbar height: 64px
Route Meta for Layout Selection
definePage({
meta: {
layout: 'blank' | 'default' | 'portal',
public: true, // skip auth guard
requiresAuth: false, // optional auth
navMode: 'event' | 'platform', // portal only
navTitle: 'Page Title', // portal platform mode only
navActiveLink: 'events', // highlight nav item in app
}
})
3. Established Page Patterns
3.1 List Page Pattern (Table + Filters + KPI Cards + Detail Drawer)
Structure: KPI metric cards → filter row → VDataTableServer → detail drawer + form dialogs
Crewli reference: apps/app/src/pages/events/[id]/persons/index.vue
Vuexy template reference: resources/.../src/pages/apps/user/list/index.vue
Components used:
- VRow/VCol (cols="12" sm="6" md="3") for 4 metric cards at top
- VCard + VAvatar (variant="tonal", rounded, size=44) + VIcon (size=28) for each metric
- AppSelect for filter dropdowns in a row
- VDataTableServer (or VDataTable) with custom column templates via
#item.[column] - VChip for status badges and type labels (with
:colorfrom status color map) - VBtn for row actions (approve, edit, delete)
- PersonDetailPanel (VNavigationDrawer) for row-click detail view
- CreatePersonDialog / EditPersonDialog (VDialog) for CRUD
- VSnackbar for success/error notifications
- VSkeletonLoader for loading state
- VAlert (type="error") with retry button for error state
KPI tiles pattern:
const tiles = computed(() => [
{ title: 'Totaal', value: meta.value?.total ?? 0, icon: 'tabler-users', color: 'primary' },
{ title: 'Goedgekeurd', value: meta.value?.total_approved ?? 0, icon: 'tabler-circle-check', color: 'success' },
{ title: 'Wachtend', value: meta.value?.pending ?? 0, icon: 'tabler-clock', color: 'warning' },
{ title: 'Afgewezen', value: meta.value?.rejected ?? 0, icon: 'tabler-x', color: 'error' },
])
Data fetching pattern:
const { data, isLoading, isError, refetch } = usePersonList(eventId, filters)
const { mutate: approve } = useApprovePerson(eventId)
const { mutate: deletePerson } = useDeletePerson(eventId)
3.2 Detail Panel Pattern (Right Drawer with Tabs)
Structure: VNavigationDrawer (temporary, right) → header with avatar + status chips → VTabs → tabbed content
Crewli reference: apps/app/src/components/persons/PersonDetailPanel.vue
Vuexy template reference: resources/.../src/views/apps/user/list/AddNewUserDrawer.vue (drawer pattern)
Components used:
- VNavigationDrawer (
location="end",temporary,width=480) - VAvatar (size=56, variant="tonal", color="primary") with initials
- VChip for status/type badges
- VBtn icon variant (variant="text", size="small") for edit/close actions
- VTabs + VTabsWindow + VTabsWindowItem for tabbed content
- VList + VListItem with
#prependslot (VIcon) for data display - VTextarea for inline-editable admin notes (save on blur)
- VSwitch for toggle fields
- VSkeletonLoader (type="article") for loading state
- VDivider between sections
Props pattern:
defineProps<{ eventId: string; orgId: string; personId: string | null }>()
const modelValue = defineModel<boolean>({ required: true })
defineEmits<{ edit: [person: Person] }>()
Interaction flow: Row click → selectedPersonId set → drawer opens → edit button emits edit → parent opens edit dialog.
Drawer scroll fix (custom CSS required for scrollable content):
.detail-drawer :deep(.v-navigation-drawer__content) { min-height: 0; }
3.3 Form Dialog Pattern (Create/Edit)
Structure: VDialog → VCard → VCardTitle → VForm → form fields → VCardActions
Crewli reference: apps/app/src/components/persons/CreatePersonDialog.vue
Vuexy template reference: resources/.../src/components/dialogs/UserInfoEditDialog.vue
Components used:
- VDialog (
max-width="500"or responsive:$vuetify.display.smAndDown ? 'auto' : 600) - VCard with VCardTitle, VCardText, VCardActions
- VForm (ref for validation)
- AppTextField / AppSelect / AppDateTimePicker / AppAutocomplete for form fields
- VBtn with
:loading="isPending"for submit - DialogCloseBtn in card header (Vuexy pattern) or VBtn icon for close
@core/utils/validatorsfor field validation (requiredValidator, emailValidator)
Error handling pattern:
const errors = ref<Record<string, string>>({})
const refVForm = ref<VForm>()
const { mutate, isPending } = useCreatePerson(eventIdRef)
function onSubmit() {
refVForm.value?.validate().then(({ valid }) => {
if (!valid) return
mutate(payload, {
onSuccess: () => { modelValue.value = false },
onError: (err) => { errors.value = err.response.data.errors },
})
})
}
<AppTextField :error-messages="errors.email" :rules="[requiredValidator, emailValidator]" />
v-model pattern for dialog visibility:
const modelValue = defineModel<boolean>({ required: true })
3.4 Dashboard / Stat Cards Pattern
Structure: VRow/VCol grid with color-coded metric cards → conditional content sections
Crewli reference: apps/app/src/components/events/EventMetricCards.vue
Vuexy template reference: resources/.../src/pages/dashboards/analytics.vue
Components used:
- VRow + VCol (cols="12" sm="6" md="3") for responsive 4-column grid
- VCard with
card-border-shadow-{color}custom class andcursor-pointer h-100 - VAvatar (
:color,variant="tonal",size=44,rounded) + VIcon (size=28) - VCardText with
d-flex align-center mb-1for icon + value row - VSkeletonLoader (type="heading") in matching grid for loading state
- VAlert (variant="tonal", type="error") with retry for error state
Color logic pattern:
const shiftFillColor = computed(() => {
if (!stats.value || stats.value.shifts_total === 0) return 'success'
const rate = stats.value.shifts_filled / stats.value.shifts_total
if (rate >= 0.9) return 'success'
if (rate >= 0.6) return 'warning'
return 'error'
})
Card template:
<VCol cols="12" sm="6" md="3">
<VCard :class="['cursor-pointer h-100', `card-border-shadow-${color}`]" @click="navigate">
<VCardText>
<div class="d-flex align-center mb-1">
<VAvatar :color="color" variant="tonal" size="44" rounded class="me-4">
<VIcon :icon="icon" size="28" />
</VAvatar>
<h4 class="text-h4 mb-0">{{ value }}</h4>
</div>
<p class="mb-1">{{ title }}</p>
<p class="mb-0">
<span class="text-heading fw-medium me-2">{{ secondary }}</span>
<span class="text-body-secondary text-sm">{{ subtitle }}</span>
</p>
</VCardText>
</VCard>
</VCol>
3.5 Tab Navigation Pattern (Event Tabs Wrapper)
Structure: Header (back + title + status chips + actions) → VTabs → slot for child page content
Crewli reference: apps/app/src/components/events/EventTabsNav.vue
Vuexy template reference: resources/.../src/pages/pages/account-settings/[tab].vue
Components used:
- VBtn (icon="tabler-arrow-left", variant="text") for back navigation
- VChip for status/type badges with color maps
- VTabs with VTab using
:toprop for route-based tab switching (no VWindow needed) - Slot for tab content — child page renders inside
- VSkeletonLoader (type="card") for loading
- VAlert (type="error") with retry for errors
Tab definition:
const tabs = [
{ label: 'Overzicht', icon: 'tabler-layout-dashboard', route: 'events-id' },
{ label: 'Personen', icon: 'tabler-users', route: 'events-id-persons' },
{ label: 'Secties & Shifts', icon: 'tabler-layout-grid', route: 'events-id-sections' },
{ label: 'Instellingen', icon: 'tabler-settings', route: 'events-id-settings' },
]
const activeTab = computed(() =>
tabs.find(t => route.name === t.route || route.name?.startsWith(`${t.route}-`))?.route
)
3.6 Settings Page Pattern (Nested Tabs)
Structure: VTabs (route/query-param based) → VWindow + VWindowItem → delegated tab components
Crewli reference: apps/app/src/pages/organisation/settings.vue
Vuexy template reference: resources/.../src/pages/pages/account-settings/[tab].vue
Two approaches from Vuexy:
- Horizontal tabs:
VTabs(class="v-tabs-pill") with VTab:toroute binding + VWindow - Vertical tabs: VRow → VCol md="4" (VTabs direction="vertical") + VCol md="8" (VWindow)
Key patterns:
v-tabs-pillclass for pill-style tabs (Vuexy custom class)disable-tab-transitionon VWindow to prevent animation delays- Route params or query params for tab state persistence
3.7 Sections & Shifts Pattern (Draggable + Table + Drawer)
Structure: Draggable section list (sidebar) → shift table (main area) → shift detail drawer
Crewli reference: apps/app/src/components/sections/SectionsShiftsPanel.vue
Components used:
vuedraggablelibrary for reorderable section list- VCard per section with click-to-activate
- VDataTable for shifts within active section
- ShiftDetailPanel (VNavigationDrawer) for shift details
- Custom CSS for drag ghost states (necessary — no Vuetify equivalent)
3.8 Confirm Dialog Pattern
Inline confirm (preferred for new code):
<VDialog v-model="confirmOpen" max-width="440">
<VCard>
<VCardTitle class="text-h6">Status wijzigen</VCardTitle>
<VCardText class="text-body-1">
Weet je zeker dat je de status wilt wijzigen naar <strong>{{ target }}</strong>?
</VCardText>
<VCardActions>
<VSpacer />
<VBtn variant="text" :disabled="isPending" @click="cancel">Annuleren</VBtn>
<VBtn color="primary" :loading="isPending" @click="confirm">Bevestigen</VBtn>
</VCardActions>
</VCard>
</VDialog>
Reusable ConfirmDialog component also available at components/dialogs/ConfirmDialog.vue with success/cancel result dialogs.
3.9 Portal Page Patterns
Event List (Card Grid)
Reference: apps/portal/src/pages/evenementen/index.vue
- VRow + VCol (cols="12" sm="6" md="4") responsive grid
- Custom EventCard component per event (gradient banner, status chip, hover animation)
- VSkeletonLoader for loading, VAlert for empty state
Event Detail (Hash-based Tabs)
Reference: apps/portal/src/pages/evenementen/[eventId].vue
- VTabs with hash-based routing (#overzicht, #rooster, #claimen, #informatie)
- VWindow + VWindowItem per tab with dedicated tab components
- StatusCard shows different UI per approval status (pending/approved/rejected)
- Conditional tab visibility based on approval status
Registration (Multi-step Form with VeeValidate + Zod)
Reference: apps/portal/src/pages/register/[eventSlug].vue
- VForm with VeeValidate field binding + Zod schemas from
@/schemas/ - Conditional form fields based on event configuration
- Real-time email duplicate checking
- Password creation for new users
Auth Pages (Centered Card)
Reference: apps/portal/src/pages/login.vue
- VCard centered on blank layout
- VTextField with password visibility toggle
- VAlert for error messages, VBtn with loading state
4. Vuetify Component Quick Reference
Preferred Vuetify components for common needs. Use these, not custom solutions.
| Need | Use | NOT |
|---|---|---|
| Data tables (server pagination) | VDataTableServer |
VDataTable or custom table |
| Data tables (client-side) | VDataTable |
Custom table markup |
| Form text input | AppTextField (@core wrapper) |
Raw VTextField or custom input |
| Form textarea | AppTextarea (@core wrapper) |
Raw VTextarea |
| Select / dropdown | AppSelect or AppAutocomplete (@core) |
Raw VSelect or custom dropdown |
| Date/time picker | AppDateTimePicker (@core, Flatpickr) |
input[type=date] or custom picker |
| Confirmation dialog | VDialog + VCard (inline pattern, §3.8) | window.confirm() |
| Side panel / drawer | VNavigationDrawer (location="end", temporary) |
Custom sidebar |
| Tabs (route-based) | VTabs + VTab (with :to for routing) |
Custom tab logic |
| Tabs (local state) | VTabs + VWindow + VWindowItem | Manual v-if switching |
| Loading: page/card | VSkeletonLoader (type="card", "article", "heading") |
Custom spinner |
| Loading: button | VBtn :loading="isPending" |
Disabled + spinner |
| Loading: global bar | VProgressLinear (indeterminate) | Custom loading bar |
| Error state | VAlert (type="error") with retry VBtn in #append slot |
Custom error div |
| Empty state | VCard (variant="outlined") + VIcon + message + VBtn |
Blank area |
| Status / type badge | VChip (:color from color map, size="small") |
Custom styled span |
| Icon buttons | VBtn (icon variant, variant="text", size="small") |
Custom icon wrapper |
| Tooltips | VTooltip (default location: top via defaults) | title attribute |
| Toast / notification | VSnackbar | Custom notification |
| User avatar | VAvatar (variant="tonal") with initials or VImg |
Custom circle div |
| Stat metric cards | VCard + VAvatar (tonal, rounded) + VIcon layout | CardStatisticsHorizontal (unless you need chart) |
| Progress indicator | VProgressLinear | Custom progress bar |
| Responsive grid | VRow + VCol with cols/sm/md/lg props |
CSS Grid or fixed widths |
| List display | VList + VListItem with #prepend/#append slots |
Custom list markup |
| Dividers | VDivider | Custom hr or border |
| Spacer in card actions | VSpacer | Custom margin/flex |
| Menu / dropdown actions | VMenu + VList + VListItem | Custom popover |
| Drag-and-drop lists | vuedraggable (external library) |
Custom drag logic |
| Rich text editing | TiptapEditor (@core) | Custom editor |
| Form validation | VForm ref + @core/utils/validators + API error mapping |
(VeeValidate + Zod only in portal registration) |
| Multi-step wizard | AppStepper (@core) | Custom step logic |
| Drawer header | AppDrawerHeaderSection (@core) | Custom header |
| Code display | AppCardCode (@core) | Custom code block |
5. Plugins & Configuration
Vuetify Defaults (plugins/vuetify/defaults.ts)
Pre-configured component defaults — no need to repeat these props in your code:
| Component | Default Props |
|---|---|
| VBtn | color: 'primary' |
| VChip | label: true |
| VList | density: 'compact', color: 'primary' |
| VTabs | color: 'primary', density: 'comfortable' |
| VSelect, VTextField, VAutocomplete | variant: 'outlined', color: 'primary', density: 'comfortable', hideDetails: 'auto' |
| VCheckbox, VRadio, VSwitch | color: 'primary', density: 'comfortable' |
| VTooltip | location: 'top' |
| VMenu | offset: '2px' |
| VExpansionPanel | expandIcon: 'tabler-chevron-right', collapseIcon: 'tabler-chevron-right' |
| VDataTable | Pagination icons: tabler-chevrons-left/right, tabler-chevron-left/right |
| VPagination | First/last: tabler-chevrons-left/right, prev/next: tabler-chevron-left/right |
Theme Colors (plugins/vuetify/theme.ts)
| Color | Hex | Usage |
|---|---|---|
| Primary | #7367F0 |
Buttons, links, active states |
| Secondary | #808390 |
Muted text, secondary actions |
| Success | #28C76F |
Approved, positive states |
| Info | #00BAD1 |
Informational states |
| Warning | #FF9F43 |
Pending, attention-needed states |
| Error | #FF4C51 |
Rejected, destructive actions |
Light theme: background #F8F7FA, surface #FFFFFF
Dark theme: background #25293C, surface #2F3349
Full grey scale: grey-50 through grey-900. Semantic on-colors: on-primary, on-secondary, etc.
Icon System (plugins/vuetify/icons.ts)
- Tabler icons via Iconify (prefix:
tabler-) - Custom SVG overrides for checkbox and radio form controls
- 40+ Vuetify icon aliases configured (expand, collapse, sort, check, clear, edit, delete, calendar, etc.)
Plugin Registration System (@core/utils/plugins.ts)
Auto-discovers and registers plugins from plugins/*.ts and plugins/*/index.ts using import.meta.glob. Numbered prefixes control load order:
1.router/— loaded first2.pinia.ts— loaded second- Everything else — alphabetical
@core Utilities
| Utility | Path | Key Exports |
|---|---|---|
| validators | @core/utils/validators.ts |
requiredValidator, emailValidator, passwordValidator, confirmedValidator, betweenValidator, integerValidator, regexValidator, alphaValidator, urlValidator, lengthValidator, alphaDashValidator |
| formatters | @core/utils/formatters.ts |
avatarText(name) → initials, kFormatter(num) → "1.5k", formatDate(value, options?), formatDateToMonthShort(value), prefixWithPlus(value) |
| helpers | @core/utils/helpers.ts |
isEmpty(value), isNullOrUndefined(value), isEmptyArray(arr), isObject(obj), isToday(date) |
| colorConverter | @core/utils/colorConverter.ts |
hexToRgb(hex), rgbaToHex(rgba, forceRemoveAlpha?) |
| vuetify | @core/utils/vuetify.ts |
resolveVuetifyTheme(defaultTheme) — resolves from cookie/system preference |
@core Composables
| Composable | Path | Purpose |
|---|---|---|
| useCookie | @core/composable/useCookie.ts |
Reactive cookie management — returns Ref<T> synced to document.cookie, JSON serialization, 30-day default expiry |
| useResponsiveSidebar | @core/composable/useResponsiveSidebar.ts |
Returns isLeftSidebarOpen ref, auto-hides on mobile (mdAndDown) |
| useSkins | @core/composable/useSkins.ts |
injectSkinClasses() — adds skin--{value} to body; layoutAttrs — computed for semi-dark vertical nav |
| useGenerateImageVariant | @core/composable/useGenerateImageVariant.ts |
Returns correct image for current theme/skin (light, dark, light-bordered, dark-bordered) |
| createUrl | @core/composable/createUrl.ts |
Reactive URL builder with query params from MaybeRefOrGetter sources |
@core Enums & Types (@core/enums.ts, @core/types.ts)
// Enums
Skins: 'default' | 'bordered'
Theme: 'light' | 'dark' | 'system'
Layout: 'vertical' | 'horizontal' | 'collapsed'
Direction: 'ltr' | 'rtl'
// Key types
UserThemeConfig // extends LayoutConfig with i18n, theme, skin, isVerticalNavSemiDark
CustomInputContent // { title, desc, value, subtitle?, icon? } for Custom Checkboxes/Radios
GridColumn // Vuetify grid column props
SortItem // { key, order } for data table sorting
Options // { page, itemsPerPage, sortBy, groupBy, search } for data table
@core Chart Libraries
| Library | Path | Components |
|---|---|---|
| ApexCharts | @core/libs/apex-chart/apexCharConfig.ts |
Configuration utilities for ApexCharts |
| Chart.js | @core/libs/chartjs/ |
7 wrappers: BarChart, BubbleChart, DoughnutChart, LineChart, PolarAreaChart, RadarChart, ScatterChart |
6. Crewli Custom Components
Event Components (apps/app/src/components/events/)
| Component | Purpose | Used By |
|---|---|---|
| EventTabsNav | Event header + horizontal tab navigation wrapper | All event sub-pages |
| EventMetricCards | 4-column KPI stat card grid with color logic | Event overview page |
| CreateEventDialog | Create event form dialog | Events list page |
| EditEventDialog | Edit event form dialog | EventTabsNav |
| CreateSubEventDialog | Create sub-event for festivals | Programmaonderdelen page |
| DeleteSubEventDialog | Delete sub-event confirmation | Programmaonderdelen page |
| RegistrationLinkCard | Copyable registration link display | EventTabsNav |
Person Components (apps/app/src/components/persons/)
| Component | Purpose | Used By |
|---|---|---|
| PersonDetailPanel | Right drawer — avatar + status + tabs (Info/Shifts/Accreditatie) | Persons list page |
| CreatePersonDialog | Create person form with crowd type + company selection | Persons list page |
| EditPersonDialog | Edit person form | Persons list page, PersonDetailPanel |
Section & Shift Components (apps/app/src/components/sections/, shifts/)
| Component | Purpose | Used By |
|---|---|---|
| SectionsShiftsPanel | Draggable section list + shift table + shift detail drawer | Sections page |
| CreateSectionDialog | Create section form | SectionsShiftsPanel |
| EditSectionDialog | Edit section form | SectionsShiftsPanel |
| CreateShiftDialog | Create shift form | SectionsShiftsPanel |
| CreateTimeSlotDialog | Create time slot form | Time slots page |
| ShiftDetailPanel | Right drawer — shift details + assignments | SectionsShiftsPanel |
| AssignShiftDialog | Assign person to shift | SectionsShiftsPanel |
| AssignPersonDialog | Assign person to shift (from person context) | ShiftDetailPanel |
Crowd List Components (apps/app/src/components/crowd-lists/)
| Component | Purpose | Used By |
|---|---|---|
| CrowdListFormDialog | Create/edit crowd list | Crowd lists page |
| CrowdListDetailPanel | Right drawer — crowd list details + members | Crowd lists page |
| AddPersonToCrowdListDialog | Add person to crowd list | CrowdListDetailPanel |
Organisation Components (apps/app/src/components/organisation/, organisations/)
| Component | Purpose | Used By |
|---|---|---|
| EditOrganisationDialog | Edit organisation details | Organisation page |
| CompanyDialog | Create/edit company | Companies page |
| CrowdTypesManager | Manage crowd types (table + CRUD) | Organisation settings |
| PersonTagsTab | Manage person tags | Organisation settings |
| RegistrationFieldTemplatesTab | Manage field templates | Organisation settings |
| EmailBrandingTab | Email branding settings (colors, logo) | Organisation settings |
Member Components (apps/app/src/components/members/)
| Component | Purpose | Used By |
|---|---|---|
| InviteMemberDialog | Invite member form | Members page |
| EditMemberRoleDialog | Change member role | Members page |
Registration Field Components (apps/app/src/components/event/)
| Component | Purpose | Used By |
|---|---|---|
| RegistrationFieldCard | Draggable field card with edit/delete | Registration fields page |
| RegistrationFieldFormDialog | Create/edit registration field | Registration fields page |
| TemplatePickerDialog | Select from predefined field templates | Registration fields page |
| ImportFromEventDialog | Import fields from another event | Registration fields page |
Layout Components (apps/app/src/components/layout/)
| Component | Purpose | Used By |
|---|---|---|
| OrganisationSwitcher | Switch between organisations | Navigation sidebar |
Portal Components (apps/portal/src/components/)
| Component | Purpose | Used By |
|---|---|---|
| portal/EventCard | Event card for grid (gradient banner, status chip, hover) | Events list |
| portal/StatusCard | Approval status display with quick action cards (pending/approved/rejected variants) | Event overview tab |
| portal/UserAvatarMenu | User menu in portal navbar | Portal layout |
| portal/AppLoadingIndicator | Global loading bar (Suspense fallback, VProgressLinear) | Portal layouts |
| event/OverzichtTab | Event overview tab (StatusCard + shift summary) | Event detail |
| event/RoosterTab | My shifts schedule tab (cancel dialog) | Event detail |
| event/ClaimenTab | Claim available shifts tab (day-grouped, confirm dialog) | Event detail |
| event/InformatieTab | Event info display tab (VList read-only) | Event detail |
7. Three-State Pattern (Loading / Error / Empty)
Every data-driven view MUST handle three states. Follow this pattern:
<!-- Loading -->
<VSkeletonLoader v-if="isLoading" type="card" />
<!-- Error -->
<VAlert v-else-if="isError" type="error" class="mb-4">
Kon data niet laden.
<template #append>
<VBtn variant="text" @click="refetch()">Opnieuw proberen</VBtn>
</template>
</VAlert>
<!-- Empty -->
<VCard v-else-if="!items?.length" variant="outlined" class="text-center pa-8">
<VIcon icon="tabler-inbox" size="48" class="mb-4 text-disabled" />
<p class="text-body-1 text-disabled">Geen resultaten gevonden.</p>
<VBtn @click="openCreate">Toevoegen</VBtn>
</VCard>
<!-- Data -->
<template v-else>
<!-- Actual content -->
</template>
Loading skeleton for grids (match the data layout):
<VRow v-if="isLoading">
<VCol v-for="n in 4" :key="n" cols="12" sm="6" md="3">
<VCard><VCardText><VSkeletonLoader type="heading" /></VCardText></VCard>
</VCol>
</VRow>
8. SCSS & Styling Reference
Available SCSS Imports
From the Vuexy template (@core/scss/):
| Category | Key Files | Purpose |
|---|---|---|
| Base | _components.scss, _dark.scss, _misc.scss |
Core component overrides |
| Layout | _default-layout.scss, _default-layout-w-vertical-nav.scss |
Layout structure styles |
| Navigation | _vertical-nav.scss, _horizontal-nav.scss |
Nav-specific styles |
| Utilities | _utilities.scss, _mixins.scss, _variables.scss |
Helper classes and mixins |
| Skins | skins/ directory |
Bordered skin variant styles |
| Template pages | template/pages/ |
Page-specific styles (auth, misc, etc.) |
| Component overrides | template/components/ |
Per-component SCSS (alert, avatar, button, card, chip, dialog, table, tabs, tooltip, etc.) |
| Library styles | template/libs/ |
ApexChart, FullCalendar, Shepherd, Swiper overrides |
Page-Level SCSS Usage
<!-- Auth pages -->
<style lang="scss">
@use "@core/scss/template/pages/page-auth";
</style>
<!-- Error pages -->
<style lang="scss">
@use "@core/scss/template/pages/misc.scss";
</style>
Custom CSS Classes from Vuexy
| Class | Purpose |
|---|---|
card-border-shadow-{color} |
Colored left border shadow on cards (primary, success, warning, error, info) |
v-tabs-pill |
Pill-style tabs |
skin--default, skin--bordered |
Skin system classes (injected on body) |
match-height |
Equal height cards in a row |
disable-tab-transition |
Prevent VWindow tab animation |
9. Cleanup Opportunities
Most custom CSS in the codebase is justified. Identified areas:
| File | CSS | Status |
|---|---|---|
SectionsShiftsPanel.vue |
.section-item { cursor: grab }, .section-ghost |
Justified — vuedraggable states, no Vuetify equivalent |
registration-fields.vue |
.field-ghost |
Justified — vuedraggable styling |
RegistrationFieldCard.vue |
.cursor-grab, .min-width-0 |
Justified — no Vuetify utility for cursor/min-width |
CrowdListDetailPanel.vue |
:deep(.v-navigation-drawer__content) { min-height: 0 } |
Justified — drawer scroll fix |
ShiftDetailPanel.vue |
Same drawer override | Justified — same fix |
EmailBrandingTab.vue |
.color-swatch sizing |
Justified — custom color preview element |
AddEditPermissionDialog.vue |
.permission-table td styling |
Minor — template code, custom padding intentional |
AppSearchHeader.vue |
.search-header padding/background |
Template code — not actively used in Crewli |
Overall: Excellent Vuetify adoption. Custom CSS is minimal and justified for cases without Vuetify equivalents (drag states, drawer scroll fixes, specialized visual elements).