Files
crewli/dev-docs/VUEXY_COMPONENTS.md
2026-05-05 20:33:33 +02:00

39 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/

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, isVerticalNavCollapsed
  • appContentWidth, appContentLayoutNav
  • horizontalNavType, 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 abilities
  • canNavigate(to) — Route-level access control using route.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
OrganizerLayout layouts/OrganizerLayout.vue All pages under pages/{events,members,organisation,account-settings,dashboard,invitations}/** Organizer experience — thin wrapper around DefaultLayoutWithVerticalNav
PortalLayout layouts/PortalLayout.vue All pages under pages/portal/** Volunteer/crew portal — custom navbar with two modes (platform / event) + mobile drawer; container max-width 1440px, navbar height 64px
PublicLayout layouts/PublicLayout.vue All pages under pages/register/** Unauthenticated scaffold for public form viewer / registration flow

PortalLayout is a custom implementation — it does NOT use @layouts VerticalNavLayout. Modes (set via route meta.navMode):

  • 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

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 logout
  • NavSearchBar.vue — Search with grouped suggestions
  • NavBarNotifications.vue — Notification bell + panel
  • NavbarShortcuts.vue — Quick access buttons
  • NavbarThemeSwitcher.vue — Light/Dark/System switcher
  • Footer.vue — Copyright footer

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 :color from 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 #prepend slot (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/validators for 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 and cursor-pointer h-100
  • VAvatar (:color, variant="tonal", size=44, rounded) + VIcon (size=28)
  • VCardText with d-flex align-center mb-1 for 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 :to prop 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 :to route binding + VWindow
  • Vertical tabs: VRow → VCol md="4" (VTabs direction="vertical") + VCol md="8" (VWindow)

Key patterns:

  • v-tabs-pill class for pill-style tabs (Vuexy custom class)
  • disable-tab-transition on 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:

  • vuedraggable library 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/app/src/pages/portal/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/app/src/pages/portal/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/app/src/pages/register/[public_token].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/app/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 first
  • 2.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/app/src/components/portal/)

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).