Skip to main content

CnWidgetWrapper

Container shell around a dashboard widget. Provides a header with icon and title, a scrollable content area, and an optional footer with action links. Accepts a styleConfig object for runtime style overrides (background, border, padding). Used internally by CnDashboardPage for all non-tile widgets.

Try it​

Loading CnWidgetWrapper playground…

Usage​

<!-- Basic wrapper -->
<CnWidgetWrapper title="My Cases" icon-url="/apps/myapp/img/icon.svg">
<MyCasesChart :data="chartData" />
</CnWidgetWrapper>

<!-- Without header (borderless, flush — for self-contained card widgets) -->
<CnWidgetWrapper :show-title="false" :borderless="true">
<CnStatsBlock :stats="kpis" />
</CnWidgetWrapper>

<!-- With NC widget object (used by CnDashboardPage internally) -->
<CnWidgetWrapper
:title="widget.title"
:icon-url="widget.iconUrl"
:icon-class="widget.iconClass"
:buttons="widget.buttons">
<CnWidgetRenderer :widget="widget" />
</CnWidgetWrapper>

<!-- With custom footer and header actions -->
<CnWidgetWrapper title="Tasks">
<template #actions>
<NcButton type="tertiary" @click="refresh">Refresh</NcButton>
</template>
<TaskList :items="tasks" />
<template #footer>
<a href="/apps/tasks">View all</a>
</template>
</CnWidgetWrapper>

Props​

PropTypeDefaultDescription
titleString'Widget'Widget title shown in the header
showTitleBooleantrueWhether to render the header bar
chromeString'default'Card chrome variant. 'default' uses the library card chrome; 'nc-dashboard' reproduces the native Nextcloud Dashboard panel exactly (translucent blurred background via --color-main-background-blur + --filter-background-blur, --border-radius-container-large corners, no border/shadow, 16px header with a 20px/700 title and 32px leading icon, content inset 16px). styleConfig overrides still layer on top.
showActionsBooleantrueWhether the header's overflow action menu (Refresh / Documentation / Request-a-feature + #action-items) renders. Set false for compact surfaces (e.g. a KPI tile) to drop the menu and free header width.
borderlessBooleanfalseRemove border and background — makes the wrapper transparent
flushBooleanfalseRemove content padding — lets content extend edge-to-edge
iconUrlStringnullImage URL for the header icon
iconClassStringnullCSS class for the header icon (e.g. Nextcloud icon class)
titleIconPositionString'right'Position of the title-icon slot in the header: 'left' places it before the title group; 'right' places it after the actions
titleIconColorStringnullCSS color value applied to the title-icon slot container (e.g. '#e74c3c')
buttonsArray[]Footer button links: [{ text, link }]
styleConfigObject{}Runtime style overrides: { backgroundColor?, borderStyle?, borderWidth?, borderColor?, borderRadius?, padding?: { top, right, bottom, left }, headerStyle?: { backgroundColor?, textColor? } }. The optional headerStyle colours the header bar per-widget.
refreshingBooleanfalseWhen bound (e.g. :refreshing="loading" around the host's refetch), the Refresh item is disabled and shows a loading spinner for exactly as long as this stays true — so the spinner reflects the real refresh time.

Slots​

SlotDescription
defaultWidget content rendered in the scrollable body area
actionsButtons or controls placed in the right side of the header
title-iconExtra icon element rendered in the header at the position controlled by titleIconPosition (left of title or right of actions)
footerCustom footer content (replaces the buttons prop rendering)

Built-in Actions menu​

CnWidgetWrapper ships with a small overflow … menu in the header — the shared CnActionsMenu — containing up to three actions. Documentation and Request-a-feature are functional without any host wiring when the wrapper is mounted under CnAppRoot; Refresh is shown only when something will handle it (see below):

  • Refresh — shown only when it will do something. showRefresh is tri-state: true/false force it on/off, and the default (null) is auto — the item renders only when a parent has attached an @refresh listener. This prevents dead buttons on widgets that can't refresh (e.g. a prop-driven CnObjectDataWidget) and on detail-page auto-body widgets where the page owns refresh. A widget that wants a manual Refresh while refreshing itself via the bus (no @refresh listener) can set :show-refresh="true" explicitly. When shown and clicked it emits @refresh, then (unless the host listener calls event.preventDefault()) emits on the @nextcloud/event-bus channel cn:widget:refresh with payload { widgetId, title }.
  • Documentation — rendered only when a documentationUrl is supplied. Opens the host-provided link in a new tab (target="_blank", rel="noopener noreferrer"); no JS handler. Apps pass the URL from the widget configuration (:documentation-url="widget.documentationUrl"); customise the wording with documentationLabel.
  • Request a feature — on by default; emits @request-feature, then (unless suppressed) auto-mounts CnSuggestFeatureModal with app + page + surface=widget:<id> context auto-filled from CnAppRoot injects. The host can override the default by binding @request-feature and calling event.preventDefault() to handle it themselves.

Force Refresh on/off per-instance with :show-refresh="true"/:show-refresh="false" (the legacy hide-refresh alias still opts out for back-compat); opt out of Request-a-feature with :show-request-feature="false" (the legacy hide-request-feature alias also still works). When everything is hidden — Refresh auto-hidden or opted out, Request-a-feature opted out, no documentationUrl, and no #action-items slot content — the overflow menu disappears entirely. To drop the entire actions area in one go — e.g. on a compact KPI tile whose only header affordance is a date chip — set :show-actions="false".

Set :widget-id so the event-bus payload + modal surface tag are stable across renames; otherwise the wrapper falls back to a slugified title.

Opting into Refresh​

Because Refresh auto-hides unless a parent attaches an @refresh listener, opting in is the act of wiring one of the patterns below. A widget that refreshes itself purely via the bus (no @refresh listener — mode 3) must also pass :show-refresh="true" so the action renders.

A widget can opt in to Refresh in one of three ways. Pick whichever fits the widget's existing reactivity model — all three are first-class.

1. Ref-callable refresh() method (canonical)​

The cleanest pattern: expose a method, let the host call it through the ref.

<template>
<CnWidgetWrapper title="Outgoing calls" widget-id="outgoing-calls-daily">
<CallsChart ref="chart" />
</CnWidgetWrapper>
</template>

<script>
export default {
// Inside CallsChart.vue:
methods: {
async refresh() {
this.data = await this.$store.dispatch('callLogs/refetch')
},
},
}
</script>

The host listens via the event-bus channel and routes by id (see mode 3 below for the wiring), or the wrapper invokes the method directly when integrated.

2. Reactive refreshTrigger prop​

Useful when the widget cannot easily expose a ref (e.g. async-loaded). The host increments a timestamp; the widget watches it.

<template>
<CnWidgetWrapper title="Outgoing calls" widget-id="outgoing-calls-daily" :show-refresh="true">
<CallsChart :refresh-trigger="callsRefreshTrigger" />
</CnWidgetWrapper>
</template>

<script>
import { subscribe } from '@nextcloud/event-bus'
export default {
data() {
return { callsRefreshTrigger: 0 }
},
mounted() {
subscribe('cn:widget:refresh', ({ widgetId }) => {
if (widgetId === 'outgoing-calls-daily') {
this.callsRefreshTrigger = Date.now()
}
})
},
}
</script>

3. Event-bus subscription (direct)​

The widget subscribes itself; no host plumbing needed.

<script>
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
export default {
props: { widgetId: { type: String, required: true } },
mounted() {
this._onRefresh = ({ widgetId }) => {
if (widgetId === this.widgetId) this.refetch()
}
subscribe('cn:widget:refresh', this._onRefresh)
},
beforeDestroy() {
unsubscribe('cn:widget:refresh', this._onRefresh)
},
methods: {
async refetch() { /* ... */ },
},
}
</script>

Reference (auto-generated)​

The tables below are generated from the SFC source via vue-docgen-cli. They reflect what's actually in CnWidgetWrapper.vue and update automatically whenever the component changes.

Props​

NameTypeRequiredDefaultDescription
titlestring() =&gt; t('nextcloud-vue', 'Widget')Widget title
showTitlebooleantrueWhether to show the header with title
chromestring'default'Chrome variant for the wrapper card. - 'default' — the library's own card chrome (opaque background, 1px border, compact header). - 'nc-dashboard' — matches the native Nextcloud Dashboard panel exactly using the same design tokens: translucent blurred background (--color-main-background-blur + --filter-background-blur), --border-radius-container-large corners, no border/shadow, a 16px header with a 20px/700 title and 32px leading icon, and content inset 16px on the sides + bottom. styleConfig overrides still layer on top so a user can customise any token.
borderlessbooleanfalseRemove border and background — makes the wrapper transparent. Useful for widgets that are self-contained cards (e.g. CnStatsBlock).
flushbooleanfalseRemove content padding — allows content to go edge-to-edge. Useful for list-style widgets where items should span the full width.
iconUrlstringnullIcon URL (image)
iconClassstringnullIcon CSS class (e.g., Nextcloud icon class)
titleIconPositionstring'right'Position of the title-icon slot in the header. 'left' places it before the title; 'right' places it after the actions.
titleIconColorstringnullExplicit CSS colour for the header icon. Overrides titleIconVariant. Must be a CSS custom property or a theme token — never a literal hex (NL Design System: the nldesign app re-themes by overriding the Nextcloud variables, and a hardcoded colour ignores that).
titleIconVariantstring'primary'Semantic colour for the header icon. Every widget's icon is coloured — primary (the theme colour) is the default, and a widget whose subject already carries a semantic meaning names it here so the icon says so at a glance (a Depublished list is error, a Concepts list warning, a Published list success).
buttonsarray[]Footer action buttons: [{ text, link }]
showActionsbooleantrueWhether the header's overflow action menu (Refresh / Documentation / Request-a-feature + any #action-items) renders. Shown by default; set false for compact surfaces — e.g. a KPI tile whose only header affordance is a date chip — to drop the menu and free header width.
styleConfig{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }\{\}Style configuration for the wrapper.
hideRefreshbooleanfalseHide the built-in Refresh item from the overflow action menu. The Refresh item is shown by default — set this when the widget has no refreshable data source (e.g. a static tile). Alias for the inverse :show-refresh="false"; either form opts out.
hideRequestFeaturebooleanfalseHide the built-in Request-a-feature item from the overflow action menu. Shown by default; set when the consuming app has no public issue tracker to link out to. Alias for the inverse :show-request-feature="false"; either form opts out.
showRefreshunionnullWhether to show the built-in Refresh item. Tri-state: - true / false — force the action on or off. - null (the default) — auto: show the action only when a parent has attached an @refresh listener (i.e. something will actually handle the refresh). This keeps widgets that can't refresh — e.g. a prop-driven CnObjectDataWidget — from showing a dead button. Widgets that refresh themselves via the cn:widget:refresh event bus (with no @refresh listener) should set :show-refresh="true" explicitly. hideRefresh (or :show-refresh="false") always wins.
showRequestFeaturebooleantrueInverse of hideRequestFeature. Defaults to true so the action renders. Set :show-request-feature="false" to hide it. Either flag hides the action.
showReportBugbooleantrueWhether the built-in "Report a bug" item renders. On by default — the trio Request a feature / Report a bug / Documentation is the contract for every widget; this exists for the rare surface that must suppress one deliberately.
showDocumentationbooleantrueWhether the built-in "Documentation" item renders. On by default, for the same reason as showReportBug. The item's target is resolved by the shared menu (see docsAnchor), so leaving it on costs the host nothing.
documentationUrlstring''Explicit documentation link for this widget, opened in a new tab. Usually unnecessary: leave it empty and set docsAnchor instead, so the link is built from the app-wide documentation base and lands on this widget's own section.
documentationLabelstring() =&gt; t('nextcloud-vue', 'Documentation')Optional pre-translated label for the Documentation action. Defaults to the lib's translation of "Documentation".
widgetIdstring''Widget id for the built-in default Refresh / Request-a-feature handlers (B2). Forwarded as the surface: "widget:&lt;id&gt;" value on the auto-mounted CnSuggestFeatureModal AND as the widgetId field on the cn:widget:refresh event-bus payload. When unset, the wrapper falls back to a slugified displayTitle, which works but is less stable than an explicit id.
docsAnchorstring''This widget's own section in the app's documentation, appended to the app-wide documentation base URL (provided by CnAppRoot) to build the Actions menu's Documentation deep-link. A bare slug becomes a #fragment. Supply it per widget type — without it the item still renders, but lands on the docs homepage rather than this widget.
reportBugUrlstring''Explicit "Report a bug" target for the Actions menu. Empty (the default) builds a new-issue deep-link on the app's own forge.
specRefstring''Optional specRef forwarded to the auto-mounted CnSuggestFeatureModal so the resulting GitHub issue links to the spec capability this widget belongs to.
refreshingbooleanfalseWhether a refresh is currently in flight. When bound by the host (e.g. :refreshing="loading" around its refetch), the Refresh item is disabled and shows a loading spinner for exactly as long as this stays true — so the spinner reflects the real refresh time.
refreshLabelstring() =&gt; t('nextcloud-vue', 'Refresh')Optional pre-translated label for the Refresh action. Defaults to the lib's translation of "Refresh" so callers usually don't need to set this.
requestFeatureLabelstring() =&gt; t('nextcloud-vue', 'Request a feature')Optional pre-translated label for the Request-a-feature action. Defaults to the lib's translation of "Request a feature".
actionsMenuLabelstring() =&gt; t('nextcloud-vue', 'Actions')Pre-translated aria-label / tooltip for the overflow menu trigger. Defaults to "Actions".
translate((key: string) => string)&#124;nullnullTranslate function. Falls back to the injected cnTranslate, which itself defaults to an identity function.

Events​

NamePayloadDescription
refreshundefinedUser clicked the Refresh item in the overflow action menu. Payload: { widgetId, title }. Handlers may call the second arg's preventDefault() to suppress the built-in default (event-bus emit on cn:widget:refresh).
request-featureundefinedUser clicked the Request a feature item. Payload: { widgetId, title }. Handlers may call the second arg's preventDefault() to suppress the built-in default (auto-opening CnSuggestFeatureModal).

Slots​

NameBindingsDescription
title-icon—
title-meta—
actions—actions Custom action buttons rendered before the
action-items—
default—
footer—

Additional props & slots​

PropTypeDefaultDescription
specRefString''Forwarded to the auto-mounted CnSuggestFeatureModal so the resulting issue links to the widget's spec capability.
documentationUrlString''When set, renders a Documentation item in the overflow menu that opens this link in a new tab. Empty hides it.
documentationLabelStringt('Documentation')Pre-translated label for the Documentation action.
refreshLabelStringt('Refresh')Pre-translated label for the Refresh action.
requestFeatureLabelStringt('Request a feature')Pre-translated label for the Request-a-feature action.
actionsMenuLabelStringt('Actions')Pre-translated aria-label / tooltip for the overflow … menu trigger.
SlotDescription
title-metaInline content rendered next to the title (e.g. the dashboard date-range chip).

Coloured header icon​

Every widget's header icon is coloured — titleIconVariant defaults to primary, so no configuration is needed. A widget whose subject already carries a meaning names it instead: a Concepts list is warning, a Published list success, a Depublished list error. titleIconColor overrides the variant with an explicit CSS variable (never a literal hex — that would ignore the nldesign app's re-theming).

The colour is published once as --cn-widget-icon-color on the header and cascades to every icon under it.

Actions menu props​

PropTypeDefaultDescription
docsAnchorString''This widget's own section in the app's documentation, appended to the app-wide base so the Documentation entry deep-links to this widget rather than the docs homepage. Prefer it over documentationUrl.
showDocumentationBooleantrueWhether the Documentation entry renders.
showReportBugBooleantrueWhether the Report-a-bug entry renders.
reportBugUrlString''Explicit bug-report target; empty builds a new-issue deep-link on the app's own forge.
titleIconVariantString'primary'Semantic header-icon colour (primary/success/warning/error/info/neutral).

Request a feature / Report a bug / Documentation render on every widget.