UI toolkit
Beyond the composables that talk to the backend, the base layers ship the building blocks your custom Vue assembles screens with. Using them keeps your screens visually and behaviorally consistent with the rest of the dashboard.
Programmatic containers — useModal / useDrawer
useModal().open(options) and useDrawer().open(options) mount a component inside a themed container and hand you a promise for its outcome. Use them when a flow needs a dialog you control from code — pick-a-record, a mini wizard, a confirmation with a form inside.
const modal = useModal();
const instance = modal.open({
title: "Pick a template",
description: "The report will be generated from it",
component: MyTemplatePicker, // any component
componentOptions: { category: "reports" }, // passed as props
});
const picked = await instance.result; // settles when the container closes
Both take the same ContainerOptions:
| Option | Purpose |
|---|---|
title / description? | Header text. |
component / componentOptions? | The body component and its props. |
headerComponent? / headerComponentOptions? | Replace the default header. |
containerId? | Container identity; generated when omitted. |
size? | useModal only — sm … 5xl. |
direction? | useDrawer only — top / bottom / left / right. |
The returned ContainerInstance exposes two members: result settles with the value the body component emits on success (or whatever you pass to close(value)), and close() dismisses the container programmatically.
TableView (Tables) opens for its drawer / modal form modes and that an ActionTarget of type drawer / modal resolves to — one look everywhere.Confirmation — useConfirm
useConfirm().confirm(options) opens the standard confirm dialog and resolves to a boolean; dismissing the dialog counts as false.
const { confirm } = useConfirm();
async function remove() {
const ok = await confirm({
title: "Delete this report?",
description: "This cannot be undone.",
confirmColor: "error",
});
if (!ok) return;
// proceed
}
ConfirmOptions takes a title and a description, optional confirmLabel / cancelLabel, and a confirmColor (primary, error, or warning).
A specialized variant, usePresenceEditWarning().warnBeforeEdit(editors), shows the "someone is already editing this" dialog from a realtime presence list and resolves the same way — a TableView uses it before opening an edit form on a row someone else holds.
Error feedback — useApiError
useApiError(error, options?) turns a failed backend call into the standard error toast: it extracts the message from the response body and runs it through i18n, falling back to a generic message when the error carries none.
try {
await $authFetch("/api/report/regenerate", { method: "POST" });
} catch (error) {
useApiError(error);
}
Success toasts are plain Nuxt UI — useToast().add({ title: "Saved" }) — nothing DMS-specific.
Async export progress — useExportJob
useExportJob().runJob(options) drives a backend export job from the client: it starts the job, polls its status into a progress toast, downloads the artifact when complete, and offers a retry on failure.
const { runJob } = useExportJob();
await runJob({
startUrl: "/api/report/export/start",
startQuery: { from: "2026-01-01" },
});
RunExportJobOptions:
| Option | Purpose |
|---|---|
startUrl | Route that starts the job. |
startMethod? | GET or POST. |
startQuery? / startBody? | Parameters for the start request. |
statusUrl? / downloadUrl? | Functions of the returned ticket; default to <startBase>/status/:jobId and /download/:jobId. |
fallbackFilename? | Function of the ticket returning a filename. The Content-Disposition filename wins when present; defaults to export-<jobId>.<extension ?? "bin">. |
labels? | Relabel the progress toast. |
retryable? | Offer a retry on failure. |
The exportJob action target is the declarative wrapper over this same runner.
Declaring keyboard shortcuts
Components define their shortcuts with Nuxt UI's defineShortcuts as usual; the DMS part is declaring them so they appear on the built-in Shortcuts settings page. Each layer lists registry files in its nuxt.config.ts, and each registry default-exports the metadata:
export default defineNuxtConfig({
cmsShortcuts: { registries: ["app/config/shortcuts-registry.ts"] },
});
import type { ComponentShortcuts } from "#imports";
export default [
{
component: "ReportViewer",
shortcuts: [
{ key: ["R"], descriptionKey: "$acme.shortcuts.refresh", component: "ReportViewer" },
],
},
] satisfies ComponentShortcuts[];
ShortcutMetadata pairs the key combination (an array of keys) with the owning component, a descriptionKey — following the $-prefix i18n convention — and an optional condition. Every layer's registries are aggregated at build time into the settings page, grouped by component.