UI Components

The starter kit includes a small set of reusable UI helpers built on top of PrimeVue. They cover consistent modal behavior, avatar uploads, file previews, tag rendering, centralized toast rendering, and loading states.

Included Components

  • AppDialog
  • AvatarUpload
  • SkImageUpload
  • ImageLightbox
  • FilePreviewModal
  • SkCard
  • PrimeVue Tag (SK-themed)
  • PrimeVue Button (SK-themed, extended severity)
  • PrimeVue Message / InlineMessage (SK-themed, extended severity)
  • ToastComponent (SK custom toast renderer)
  • ConfirmDialogComponent
  • MimePickerField
  • ToggleFeatureCard
  • SkPageLoader
  • TurnstileWidget
  • skeleton helpers: PageLoading, SkeletonBox, SkeletonCard, SkeletonTable, SkeletonText
  • FileManager component for media-oriented flows

Global Overlays

AdminLayout.vue renders these once for the whole admin area:

  • @lvntr/components/ui/ConfirmDialogComponent.vue
  • @lvntr/components/ui/ToastComponent.vue
  • @lvntr/components/ui/AppDialog.vue
  • @lvntr/components/ui/ImageLightbox.vue

These components are meant to be triggered from composables and page logic instead of being recreated in every page.

ImageLightbox and FilePreviewModal

File previews now use two different UI paths:

  • ImageLightbox for fullscreen image viewing
  • FilePreviewModal for PDF, video, audio, text, and other non-image previews

Open ImageLightbox through useImageLightbox(). Open FilePreviewModal through useDialog().

AppDialog and useDialog()

AppDialog works with useDialog() to render dynamic Vue components in a single shared dialog.

import { useDialog } from '@/composables/useDialog';
import UserForm from '@/pages/Admin/Users/components/UserForm.vue';

const dialog = useDialog();

dialog.open(UserForm, { inDialog: true }, 'Edit user', {
    refreshKey: 'users-table',
    width: '640px',
});

Async mode

Use openAsync when the dialog component needs data fetched from the server before it can render:

await dialog.openAsync(UserForm, '/roles/1/data', 'Edit user', {
    refreshKey: 'users-table',
    mapResponse: (data) => ({ role: data }),
});

ConfirmDialogComponent and useConfirm()

The confirm dialog is globally mounted and styled once. Pages only call useConfirm().

import { router } from '@inertiajs/vue3';
import { useConfirm } from '@/composables/useConfirm';

const { confirmDelete } = useConfirm();

confirmDelete(() => {
    router.delete('/users/1');
});

ToastComponent

ToastComponent is the shared PrimeVue toast container. In this project, it is typically fed by:

  • flash messages from AdminLayout.vue
  • useApi() error handling

SkCard

SkCard is the shared wrapper around PrimeVue Card used by SkForm (and intended for SkDatatable and page-level cards). It guarantees a single, consistent caption header: title text on the left, an optional #title-end slot on the right (for action buttons, badges, status indicators), subtitle directly below, and a bottom divider that separates the whole caption block from the content.

<script setup lang="ts">
    import SkCard from '@lvntr/components/ui/SkCard.vue';
</script>

<template>
    <SkCard title="Users" subtitle="Active accounts">
        <template #title-end>
            <Button icon="pi pi-plus" :label="$t('users.create')" @click="open" />
        </template>

        <SkDatatable :config="config" />
    </SkCard>

    <!-- transparent shell — no background, shadow, padding, or divider visuals -->
    <SkCard transparent>
        <p>Anything goes here.</p>
    </SkCard>

    <!-- hide the caption divider when the section is purely decorative -->
    <SkCard title="Notes" :divider="false">
        <p>Quick note.</p>
    </SkCard>
</template>

Props:

  • title?: string — shorthand for the #title slot. Pass a translated string (no internal $t call).
  • subtitle?: string — shorthand for the #subtitle slot.
  • transparent?: boolean (default false) — when true, the card renders without background, border, shadow, or padding. Useful inside dialogs or as an invisible grouping wrapper.
  • divider?: boolean (default true) — draws a bottom border under the caption block (title + subtitle) so it reads as a distinct header above the content. Themed via --p-surface-200 (light) / --p-surface-700 (dark).
  • pt?: Record<string, any> — extra PrimeVue Card passthrough. Merged with internal pt; consumer keys win on conflicts.

Slots:

  • header, title, subtitle, content, footer — pass through to PrimeVue Card. The default slot also maps to content, so <SkCard>…</SkCard> is equivalent to <SkCard><template #content>…</template></SkCard>.
  • title-end — rendered to the right of the title in the same flex row.

Notes:

  • SkCard is inheritAttrs: false internally but forwards class onto the Card root via useAttrs, so <SkCard class="my-cls"> works as expected (plain class fallthrough is otherwise blocked by PrimeVue Card's own inheritAttrs: false).
  • The divider only renders when the caption is present and divider is true (default). A card without a title and subtitle gets no divider line.

Tag (PrimeVue)

The kit standardizes on PrimeVue's <Tag>, repainted to the SK palette. It is auto-imported (no import needed), and its severity accepts the 6 PrimeVue severities plus the supported SK palette names — so you get the full palette with no component patch (the theme targets the data-p attribute PrimeVue emits). SkDatatable tag columns render through this same <Tag>.

<template>
    <Tag value="Active" severity="success" />
    <Tag value="Pending" severity="amber" rounded class="p-tag-soft" />
    <Tag value="Verified" icon="pi pi-check" severity="emerald" class="p-tag-outlined" />
    <Tag value="Indigo" severity="indigo" />
</template>

Severity values:

  • the 6 built-ins: success, info, warn, danger, secondary, contrast
  • Tailwind families: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, slate, gray, zinc, neutral, stone
  • SK custom families: mauve, olive, mist, taupe

Variants — opt-in via class, since PrimeVue Tag has no variant prop:

  • p-tag-soft — lighter, tinted fill
  • p-tag-outlined — border only
  • p-tag-dot — leading status dot
  • p-tag-sm / p-tag-lg — sizes
  • rounded (native prop) — pill shape

All variants are themed for light and dark mode. See the Components → Tag showcase page (/sk-components) for every variant and color.

Button (PrimeVue)

PrimeVue Button is auto-imported and its severity prop accepts the 8 built-in PrimeVue values plus any Tailwind color family or SK custom color — the same extended palette the Tag uses. The theme targets the data-p-severity attribute that PrimeVue emits on the button root.

Built-in severities (8): primary (no prop, default), secondary, success, info, warn, help, danger, contrast.

Extended color values (same Tailwind + SK families as Tag): red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, slate, gray, zinc, neutral, stone, mauve, olive, mist, taupe.

<template>
    <Button label="Save" />
    <Button label="Delete" severity="danger" outlined />
    <Button label="Approve" severity="emerald" />
    <Button label="Tag" severity="indigo" rounded />
    <Button icon="pi pi-cog" severity="secondary" text />
</template>

Variants are the standard PrimeVue button props (outlined, text, raised, rounded, size, loading, disabled). No extra class is needed for color variants — severity alone drives the full color.

Destructive actions must use severity="danger" (outlined or filled depending on prominence). See the Components → Button showcase page (/sk-components) for the full per-color matrix.

Message and InlineMessage (PrimeVue)

PrimeVue Message and InlineMessage are auto-imported and repainted to the SK palette. Their severity accepts the same extended color set as Button and Tag. The theme targets the data-p attribute on Message and the p-inlinemessage-<severity> class on InlineMessage.

Built-in severities (6): success, info, warn, danger, secondary, contrast.

Extended color values: all Tailwind families and SK custom families (same list as Button/Tag above).

Message supports four layout variants:

  • accent (default) — tinted left-border banner; use :closable="false" for persistent notices
  • fill — solid-color filled banner, opt in with class="p-message-fill"
  • outlined — border-only, opt in with variant="outlined"
  • simple — minimal, no border or background, opt in with variant="simple"
<template>
    <!-- accent (default) -->
    <Message severity="info" :closable="false">Your changes were saved.</Message>

    <!-- fill variant -->
    <Message severity="success" icon="pi pi-check-circle" class="p-message-fill">
        <div class="font-semibold">Published</div>
        <div class="text-[12.5px] opacity-80">All users can see this post.</div>
    </Message>

    <!-- outlined variant -->
    <Message severity="warn" variant="outlined" :closable="false">Review required.</Message>

    <!-- InlineMessage — inline, no dismiss -->
    <InlineMessage severity="danger">Field is required.</InlineMessage>

    <!-- Extended color -->
    <Message severity="indigo">Custom severity.</Message>
</template>

See the Components → Message showcase page (/sk-components) for every variant and color.

Toast (SK custom renderer)

ToastComponent wraps PrimeVue's <Toast> with a fully custom #container template. Because the template is custom, PrimeVue's native severity markup does not apply — styling is driven entirely by .sk-toast-<severity> CSS classes in theme/main/components/toast.css. All toast.add() calls go through useToast() from PrimeVue.

Built-in severities: success, info, warn, error, secondary, contrast.

Extended color values: all Tailwind families and SK custom families (same list as Button/Tag above). Pass any family name as severity and the toast picks up the matching sk-toast-<name> color token.

Variants (opt in via styleClass):

  • Default (accent) — tinted background, colored accent bar, progress bar
  • sk-toast-outlined — border-only shell
  • sk-toast-solid — fully filled, inverted text

Extra options beyond the standard ToastMessageOptions:

  • icon — PrimeVue icon class (e.g. 'pi pi-check-circle'); falls back to a per-severity default if omitted
  • styleClass — variant class ('sk-toast-solid' / 'sk-toast-outlined')
  • actions — pill-button array: { label: string; command?: () => void; primary?: boolean; dismiss?: boolean }
import { useToast } from 'primevue/usetoast';

const toast = useToast();

// Simple info toast
toast.add({
    severity: 'info',
    summary: 'Saved',
    detail: 'Your changes have been applied.',
    group: 'bc',
    life: 4000,
});

// Custom icon + solid variant
toast.add({
    severity: 'success',
    summary: 'Published',
    icon: 'pi pi-globe',
    styleClass: 'sk-toast-solid',
    group: 'bc',
    life: 3000,
});

// Action buttons (sticky — no life)
toast.add({
    severity: 'warn',
    summary: 'Delete item?',
    detail: 'This cannot be undone.',
    icon: 'pi pi-exclamation-triangle',
    group: 'bc',
    actions: [
        { label: 'Delete', primary: true, command: () => doDelete() },
        { label: 'Cancel' },
    ],
});

group: 'bc' is required — ToastComponent is registered on the bc group. See the Components → Toast showcase page (/sk-components) for live examples.

AvatarUpload

Avatar upload is a ready-made image picker/uploader component.

<AvatarUpload :avatar-url="user.avatar_url" upload-url="/user/avatar" delete-url="/user/avatar" />

Built-in behavior:

  • instant preview with FileReader
  • upload via fetch
  • delete confirmation through useConfirm()
  • Inertia reload after successful upload or delete

PageLoading

PageLoading.vue shows a skeleton overlay during Inertia navigation.

<PageLoading>
    <template #skeleton>
        <SkeletonTable :rows="6" :columns="4" />
    </template>

    <YourPageContent />
</PageLoading>

Other skeleton helpers available under @lvntr/components/Skeleton/:

  • SkeletonBox — generic placeholder box
  • SkeletonCard — card-shaped loading block
  • SkeletonTable — table-shaped loading block with configurable rows and columns
  • SkeletonText — text-line placeholders

SkImageUpload

SkImageUpload is a generic image upload slot for settings-style brand assets (logo, favicon). It mirrors AvatarUpload's optimistic-preview pattern — instant FileReader preview, fetch upload, and an Inertia partial reload on success — but is designed for rectangular/non-avatar contexts.

<SkImageUpload
    :preview-url="settings.logo_url"
    upload-url="/admin/settings/logo"
    field-name="logo"
    response-key="logo_url"
    accept="image/png,image/svg+xml"
    label="Light logo"
    hint="Recommended: 300×80 px, PNG or SVG"
    upload-label="Upload"
    remove-label="Remove"
    remove-confirm="Remove the logo?"
    variant="logo-light"
    layout="stacked"
    :reload-only="['settings']"
/>

Props:

  • previewUrl?: string | null — canonical server URL; null shows a placeholder icon
  • uploadUrl: string — endpoint for both POST (upload) and DELETE (remove)
  • fieldName: string — FormData key for the file (e.g. logo, favicon)
  • responseKey: string — key under json.data holding the new URL after upload
  • accept: string — passed to <input accept>
  • label: string, hint: string — already-translated display strings
  • uploadLabel: string, removeLabel: string, removeConfirm: string — already-translated button/confirm strings
  • variant?: 'logo-light' | 'logo-dark' | 'favicon' (default 'logo-light') — preview box style; logo-dark uses a dark backdrop; favicon renders a square box
  • layout?: 'stacked' | 'row' (default 'row') — stacked stacks preview + buttons vertically (used in logo grids); row puts everything inline
  • reloadOnly?: string[] (default ['settings']) — Inertia partial-reload prop keys

MimePickerField

MimePickerField is a checkbox-group MIME-type picker used in the File Manager settings. It renders categories (Images, Documents, Archive) of MIME options as labeled checkboxes and emits a string[] of selected MIME strings.

<MimePickerField v-model="settings.allowed_mimes" />

Props:

  • modelValue?: string[] | null — currently selected MIME types
  • categories?: MimeCategory[] — override the default category list (Images / Documents / Archive). Each entry: { titleKey: string; options: { label: string; value: string; icon: string }[] }

Default MIME groups: JPEG, PNG, GIF, WebP (images); PDF, DOC/DOCX, XLS/XLSX, plain text, CSV (documents); ZIP (archive).

ToggleFeatureCard

ToggleFeatureCard is a styled card-row with an integrated toggle switch, used for feature-flag settings UIs (e.g. enabling/disabling File Manager modules).

<ToggleFeatureCard
    v-model="settings.share_enabled"
    label="Share links"
    description="Allow users to generate public share links for files."
    icon="pi-share-alt"
/>

Props:

  • modelValue?: boolean (default false) — bound toggle state
  • label: string — feature name, rendered in bold
  • description?: string — secondary text below the label
  • icon?: string — PrimeVue icon name (without pi prefix, e.g. 'pi-share-alt'); renders a tinted icon badge on the left when provided

The card border and background shift to a primary-tinted color when the toggle is on.

SkPageLoader

SkPageLoader is a full-screen animated loading overlay that appears during Inertia page switches. It replaces the default NProgress top bar with an animated radial grid background and a staggered letter-wave word. The animation and theming are defined in theme/main/components/page-loader.css.

It is opt-in — the shipped scaffold does not mount it. To enable it, add <SkPageLoader/> to the overlays slot of your AdminLayout.vue, alongside the other global overlays, and import it from the component library:

import SkPageLoader from '@lvntr/components/ui/SkPageLoader.vue';

<template #overlays>
    <ConfirmDialogComponent />
    <ToastComponent />
    <AppDialog />
    <ImageLightbox />
    <SkPageLoader :delay="250" />
</template>

Props:

  • delay?: number (default 250) — milliseconds before the overlay appears; prevents flashing on instant navigations

SkPageLoader uses usePageLoading() internally and reads the localized sk-layout.loading string for the animated word.

TurnstileWidget

TurnstileWidget embeds a Cloudflare Turnstile challenge widget. It is used by the kit's auth pages (login, register, password reset) and can be placed in any form that needs bot protection.

For full configuration and backend setup, see the Authentication documentation.

<TurnstileWidget v-model="form.turnstile_token" />

Props:

  • modelValue: string — the Turnstile token emitted after a successful challenge. Bind it to your form's token field and include it in the submit payload.

Exposes (via defineExpose):

  • reset() — reset the widget (useful after a failed form submit)
  • loadFailed: Ref<boolean>true if the Cloudflare script failed to load

The widget only renders when page.props.turnstile.enabled is true. If Turnstile is disabled in the app settings, the component renders nothing. The Cloudflare script is loaded lazily on mount and is shared across widget instances on the same page.

Recommendation

Prefer reusing these components before introducing new local variants. That keeps the admin panel visually consistent and makes future kit updates easier.