MagicToast
MagicToast let’s you trigger and display toasts from anywhere.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-demo" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
</script>Overview
Anatomy
<template>
<magic-toast-provider id="your-toast-id" />
</template>
<script>
import { defineAsyncComponent } from 'vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const component = defineAsyncComponent(() => import('./YourToast.vue'))
const { add } = useMagicToast('your-toast-id')
function handleClick() {
add({ component })
}
</script>Installation
CLI
Add @maas/vue-equipment to your dependencies.
pnpm install @maas/vue-equipmentnpm install @maas/vue-equipmentyarn add @maas/vue-equipmentbun install @maas/vue-equipmentVue
If you are using Vue, import and add MagicToastPlugin to your app.
import { createApp } from 'vue'
import { MagicToastPlugin } from '@maas/vue-equipment/plugins/MagicToast'
const app = createApp({})
app.use(MagicToastPlugin)Nuxt
The toaster is available as a Nuxt module. In your Nuxt config file add @maas/vue-equipment/nuxt to your modules and add MagicToast to the plugins in your configuration.
export default defineNuxtConfig({
modules: ['@maas/vue-equipment/nuxt'],
vueEquipment: {
plugins: ['MagicToast'],
},
})Composable
In order to interact with the toaster from anywhere within your app, we provide a useMagicToaster composable. Import it directly when needed.
const component = defineAsyncComponent(() => import('./YourToast.vue'))
const { add } = useMagicToast('your-toast-id')
function handleClick() {
add({ component })
}TIP
If you have installed the toaster as a Nuxt module, the composable will be auto-imported and is automatically available in your Nuxt app.
Peer Dependencies
If you haven’t installed the required peer dependencies automatically, you’ll need to install the following packages manually.
| Package |
|---|
@nuxt/kit |
@vueuse/core |
defu |
Installation
pnpm install @nuxt/kit @vueuse/core defunpm install @nuxt/kit @vueuse/core defuyarn add @nuxt/kit @vueuse/core defubun install @nuxt/kit @vueuse/core defuAPI Reference
MagicToastProvider
The MagicToastProvider wraps the toaster and configures it according to the provided options.
Props
| Prop | Type | Required |
|---|---|---|
MaybeRef<string> | true | |
MagicToastOptions | false |
Options
| Option | Type | Default |
|---|---|---|
boolean | false | |
Position | bottom | |
number | 0 | |
boolean | false | |
auto | ||
boolean | object | object | |
boolean | true | |
string | body | |
boolean | false | |
string | magic-toast | |
click | ||
number | 3 | |
number | 300 | |
— | ||
boolean | false | |
number | 8 | |
threshold.distance | number | 32 |
number | 1 |
CSS Variables
| Variable | Default |
|---|---|
--magic-toast-padding-y | 1rem |
--magic-toast-padding-x | 1rem |
--magic-toast-gap | 0.75rem |
--magic-toast-animation-duration | 175ms |
--magic-toast-scale-factor | 0.05 |
--magic-toast-overlap-y | 1rem |
--magic-toast-position | fixed |
--magic-toast-inset | 0 |
--magic-toast-width | 100% |
--magic-toast-height | 100% |
--magic-toast-z-index | 999 |
MagicToastView
Renders a single toast inside the toaster. Used internally.
| Variable | Default |
|---|---|
--magic-toast-view-transition | all var(--magic-toast-animation-duration) var(--ease-in-out) |
--magic-toast-view-cursor | grab |
--magic-toast-view-cursor-dragging | grabbing |
useMagicToast
Interact with a toaster instance from anywhere in your app. Pass the same id used on the corresponding MagicToastProvider.
const {
add,
remove,
clear,
hide,
show,
expand,
collapse,
toasts,
count,
hidden,
} = useMagicToast('your-toast-id')add
Adds a toast to the stack and returns its id.
| Argument | Type | Required |
|---|---|---|
Component | true | |
Record<string, unknown> | false | |
Slots | false | |
MagicToastAddOptions | undefined | |
string | false |
const id = add({ component, props: { message: 'Saved!' } })remove
Removes a toast by id.
remove(id)clear
Removes all toasts, including any currently hidden via hide(). Optionally pass a transition name to use while clearing, instead of the toaster’s configured transition option.
clear()
clear('my-clear-transition')hide / show
Temporarily hide the entire toast stack and restore it later, preserving each toast’s state (props, timers, etc.) in between. Auto-dismiss timers are paused while hidden and resume when shown again. Both optionally take a transition name to use instead of the toaster’s configured transition option. Calling hide() with nothing to hide, or show() with nothing hidden, is a no-op.
Calling add() while the stack is hidden reveals it again immediately, so a new toast is never silently queued out of sight — it’s equivalent to calling show() first.
hide()
show()
hide('my-hide-transition')
show('my-show-transition')expand / collapse
Expand or collapse the toast stack, mirroring what layout.expand: 'click' | 'hover' does on interaction.
expand()
collapse()State
| Property | Type | Description |
|---|---|---|
toasts | ComputedRef<ToastView[]> | The currently active toasts. |
count | ComputedRef<number> | Number of currently active toasts. |
hidden | ComputedRef<number> | Number of toasts currently stashed via hide(). Useful for disabling hide()/show() controls when there’s nothing to hide or restore. |
Events
The toaster emits the following events through MagicEmitter. Listen to them with useMagicEmitter.
| Event | Payload | Description |
|---|---|---|
beforeEnter | id | Fired before a toast’s enter transition starts. |
enter | id | Fired when a toast’s enter transition starts. |
afterEnter | id | Fired after a toast’s enter transition ends. |
beforeLeave | id | Fired before a toast’s leave transition starts. |
leave | id | Fired when a toast’s leave transition starts. |
afterLeave | id | Fired after a toast’s leave transition ends. |
beforeDrag | { id, x, y } | Fired when a toast starts being dragged. |
drag | { id, x, y } | Fired continuously while a toast is dragged. x and y are the current translation in pixels. |
afterDrag | { id, x, y } | Fired when a toast stops being dragged. |
Errors
| Source | Error Code | Message |
|---|---|---|
MagicToastView | missing_instance_id | MagicToastView must be used within a MagicToastProvider |
Examples
Position
Set the position option to anchor the toaster to any edge or corner of the viewport.
<template>
<div class="w-full flex gap-2">
<m-button @click="onClick">Add Toast</m-button>
<m-select
v-model="position"
:options="options"
label="Position"
placeholder="Position"
/>
</div>
<magic-toast-provider
id="magic-toast-position-demo"
:options="{
position,
}"
/>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton, MSelect } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-position-demo')
const position = ref('bottom-right')
const options = [
{ value: 'top-right', label: 'Top Right' },
{ value: 'top', label: 'Top' },
{ value: 'top-left', label: 'Top Left' },
{ value: 'left', label: 'Left' },
{ value: 'bottom-left', label: 'Bottom Left' },
{ value: 'bottom', label: 'Bottom' },
{ value: 'bottom-right', label: 'Bottom Right' },
{ value: 'right', label: 'Right' },
]
function onClick() {
const props = {
message: options.find((p) => p.value === position.value)?.label,
}
add({ component, props })
}
</script>Expanded
Set initial.expanded: true together with layout.expand: false to always render the stack fully expanded, instead of collapsed into an overlapping pile.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-expanded-demo" :options="options" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-expanded-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
const options = {
layout: {
expand: false,
},
initial: {
expanded: true,
},
}
</script>Collapsed
Set layout.expand: false to keep the stack collapsed permanently, with no click or hover interaction to expand it.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-collapsed-demo" :options="options" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-collapsed-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
const options = {
layout: {
expand: false,
},
}
</script>Hover
Set layout.expand: 'hover' to expand the stack while the pointer is over it, and collapse it again on mouse leave.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-hover-demo" :options="options" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-hover-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
const options = {
layout: {
expand: 'hover',
},
}
</script>No Drag
Set drag: { disabled: true } on the provider to disable drag-to-dismiss for every toast in the stack.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-no-drag-demo" :options="options" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-no-drag-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
const options = {
drag: { disabled: true },
}
</script>Per-Toast Options
Pass options to add() to override provider defaults for a single toast, without affecting the rest of the stack. It accepts the per-toast subset of the toaster options — duration, drag, threshold and animation.
<template>
<div class="grid w-full grid-cols-2 items-end gap-3 sm:grid-cols-3">
<m-toggle v-model="draggable" label="Draggable" />
<m-select
v-model="direction"
:options="directionOptions"
label="Drag direction"
placeholder="Drag direction"
/>
<m-input v-model="duration" type="number" label="Auto-dismiss (ms)" />
<m-input v-model="distance" type="number" label="Dismiss distance (px)" />
<m-input v-model="snapDuration" type="number" label="Snap duration (ms)" />
<m-button class="w-full" @click="onClick">Add Toast</m-button>
</div>
<magic-toast-provider
:id="id"
:options="{
position: 'bottom-right',
layout: { expand: false, max: 10 },
initial: { expanded: true },
}"
/>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, shallowRef } from 'vue'
import { MButton, MInput, MSelect, MToggle } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
import type { MagicToastAddOptions } from '@maas/vue-equipment/plugins/MagicToast'
const id = 'magic-toast-per-toast-options-demo'
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast(id)
const draggable = shallowRef(true)
const duration = shallowRef(0)
const distance = shallowRef(32)
const snapDuration = shallowRef(300)
const direction = shallowRef('auto')
const directionOptions = [
{ value: 'auto', label: 'Auto' },
{ value: 'left', label: 'Left' },
{ value: 'right', label: 'Right' },
{ value: 'up', label: 'Up' },
{ value: 'down', label: 'Down' },
{ value: 'left-right', label: 'Left & Right' },
{ value: 'up-down', label: 'Up & Down' },
]
type Direction = NonNullable<
NonNullable<MagicToastAddOptions['drag']>['direction']
>
const resolvedDirection = computed<Direction>(() => {
switch (direction.value) {
case 'left-right':
return ['left', 'right']
case 'up-down':
return ['up', 'down']
case 'left':
case 'right':
case 'up':
case 'down':
return direction.value
default:
return 'auto'
}
})
function onClick() {
add({
component,
props: { message: draggable.value ? 'Drag me around' : 'Not draggable' },
options: {
duration: Number(duration.value),
drag: {
disabled: !draggable.value,
direction: resolvedDirection.value,
},
threshold: { distance: Number(distance.value) },
animation: { snap: { duration: Number(snapDuration.value) } },
},
})
}
</script>Drag Direction
Use drag.direction to pin the drag-out side ('left' | 'right' | 'up' | 'down') regardless of position, or pass a same-axis pair like ['left', 'right'] to allow dragging either way.
<template>
<div class="flex w-full gap-2">
<m-button @click="onClick">Add Toast</m-button>
<m-select
v-model="position"
:options="positionOptions"
label="Position"
placeholder="Position"
/>
<m-select
v-model="direction"
:options="directionOptions"
label="Drag direction"
placeholder="Drag direction"
/>
</div>
<magic-toast-provider
id="magic-toast-drag-direction-demo"
:options="{
position,
drag: { direction: resolvedDirection },
layout: {
expand: false,
max: 10,
},
initial: {
expanded: true,
},
}"
/>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from 'vue'
import { MButton, MSelect } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-drag-direction-demo')
const position = ref('bottom-right')
const direction = ref('left-right')
const positionOptions = [
{ value: 'top-right', label: 'Top Right' },
{ value: 'top', label: 'Top' },
{ value: 'top-left', label: 'Top Left' },
{ value: 'left', label: 'Left' },
{ value: 'bottom-left', label: 'Bottom Left' },
{ value: 'bottom', label: 'Bottom' },
{ value: 'bottom-right', label: 'Bottom Right' },
{ value: 'right', label: 'Right' },
]
const directionOptions = [
{ value: 'auto', label: 'Auto' },
{ value: 'left', label: 'Left' },
{ value: 'right', label: 'Right' },
{ value: 'up', label: 'Up' },
{ value: 'down', label: 'Down' },
{ value: 'left-right', label: 'Left & Right' },
{ value: 'up-down', label: 'Up & Down' },
]
// MSelect only supports a single value, so the same-axis pairs are
// offered as presets here and expanded into an array for the option.
const resolvedDirection = computed(() => {
if (direction.value === 'left-right') {
return ['left', 'right'] as const
}
if (direction.value === 'up-down') {
return ['up', 'down'] as const
}
return direction.value
})
// Mirrors the fallback in useToastDrag: when direction is 'auto',
// the actual drag axis/side is derived from the anchor position.
const messageDirection = computed(() => {
if (direction.value === 'left-right') {
return 'left or right'
}
if (direction.value === 'up-down') {
return 'up or down'
}
if (direction.value !== 'auto') {
return direction.value
}
switch (position.value) {
case 'top':
case 'top-left':
case 'top-right':
return 'up'
case 'bottom':
case 'bottom-left':
case 'bottom-right':
return 'down'
case 'left':
return 'left'
case 'right':
return 'right'
default:
return direction.value
}
})
function onClick() {
add({
component,
props: { message: `Drag me ${messageDirection.value} to dismiss` },
})
}
</script>Opacity Fade
The toast does not fade on its own. Listen to the drag event, derive an opacity from the drag distance, and reset it on afterDrag.
<template>
<div class="flex w-full gap-2">
<m-button @click="onClick">Add Toast</m-button>
</div>
<magic-toast-provider
:id="id"
:options="{
position: 'bottom-right',
threshold: { distance: dismissThreshold },
layout: { expand: false, max: 1 },
initial: { expanded: true },
}"
/>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, reactive, onBeforeUnmount } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
import { useMagicEmitter } from '@maas/vue-equipment/plugins/MagicEmitter'
import type { MagicEmitterEvents } from '@maas/vue-equipment/plugins/MagicEmitter'
const id = 'magic-toast-opacity-fade-demo'
const component = defineAsyncComponent(
() => import('./components/FadeDemoToast.vue')
)
const { add } = useMagicToast(id)
const emitter = useMagicEmitter()
// Distance that dismisses the toast; the fade spans four times that
const dismissThreshold = 60
const fadeDistance = dismissThreshold * 4
const props = reactive({ message: 'Drag me away to fade me out', opacity: 1 })
// drag is shared across plugins, so the payload is a union — narrow it to the
// x/y shape before use. Derive opacity from the drag distance and reset it once
// the drag ends, entirely through the emitter — the toast no longer fades on its own.
function onDrag(payload: MagicEmitterEvents['drag']) {
if (payload.id !== id || !('x' in payload)) {
return
}
const distance = Math.max(Math.abs(payload.x), Math.abs(payload.y))
props.opacity = 1 - Math.min(distance / fadeDistance, 1)
}
function onAfterDrag(payload: MagicEmitterEvents['afterDrag']) {
if (payload.id !== id || !('x' in payload)) {
return
}
props.opacity = 1
}
emitter.on('drag', onDrag)
emitter.on('afterDrag', onAfterDrag)
onBeforeUnmount(() => {
emitter.off('drag', onDrag)
emitter.off('afterDrag', onAfterDrag)
})
function onClick() {
add({ component, props })
}
</script>Clear All
Call clear() to remove every toast in the stack at once, optionally with a custom transition name.
<template>
<div class="flex gap-2">
<m-button @click="onClick">Add Toast</m-button>
<m-button @click="clear">Clear All</m-button>
</div>
<magic-toast-provider id="magic-toast-clear-all-demo" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add, clear } = useMagicToast('magic-toast-clear-all-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
</script>Hide and Show
Call hide() to temporarily remove the whole stack from view, and show() to restore it exactly as it was — props, timers and all.
<template>
<div class="flex gap-2">
<m-button @click="onClick">Add Toast</m-button>
<m-button :disabled="toastCount === 0" @click="hide">Hide</m-button>
<m-button :disabled="hidden === 0" @click="show">Show</m-button>
</div>
<magic-toast-provider
id="magic-toast-hide-show-demo"
:options="{
layout: {
expand: false,
max: 10,
},
initial: {
expanded: true,
},
}"
/>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const {
add,
hide,
show,
count: toastCount,
hidden,
} = useMagicToast('magic-toast-hide-show-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
</script>Hide and Show with a Custom Transition
Pass a transition name to hide()/show() to swap in a different animation just for that call, without changing the toaster’s configured transition option.
<template>
<div class="flex gap-2">
<m-button @click="onClick">Add Toast</m-button>
<m-button :disabled="toastCount === 0" @click="hide('zoom')">
Hide (zoom)
</m-button>
<m-button :disabled="hidden === 0" @click="show('zoom')">
Show (zoom)
</m-button>
</div>
<magic-toast-provider
id="magic-toast-hide-show-transition-demo"
:options="{
layout: {
expand: false,
max: 10,
},
initial: {
expanded: true,
},
}"
/>
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
import '@maas/vue-equipment/utils/css/keyframes/zoom-in.css'
import '@maas/vue-equipment/utils/css/keyframes/zoom-out.css'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const {
add,
hide,
show,
count: toastCount,
hidden,
} = useMagicToast('magic-toast-hide-show-transition-demo')
function onClick() {
count.value += 1
const props = {
message: count.value % 2 ? 'Magic as a Service™' : 'Vue Equipment',
}
add({ component, props })
}
</script>
<style>
/* Custom transition used only for hide('zoom') / show('zoom'),
passed as the transition name override on those calls. */
.zoom-enter-active {
animation: zoom-in var(--magic-toast-animation-duration) var(--ease-in-out);
}
.zoom-leave-active {
animation: zoom-out var(--magic-toast-animation-duration) var(--ease-in-out);
position: absolute;
}
.zoom-move {
transition: all var(--magic-toast-animation-duration) var(--ease-in-out);
}
</style>Slot
Pass slots to add() to render arbitrary content inside the toast component via its default slot.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-slot-demo" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref, h, type Slots } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const { add } = useMagicToast('magic-toast-slot-demo')
function onClick() {
count.value += 1
// Break reactivity for count
const mappedCount = count.value
const slots: Slots = {
default: () => [h('span', `Message #${mappedCount}`)],
}
add({ component, slots })
}
</script>Slotted Component
Pass a component (instead of plain content) through slots, so the toast can render nested, composed markup.
<template>
<m-button @click="onClick">Add Toast</m-button>
<magic-toast-provider id="magic-toast-nested-slot-demo" />
</template>
<script lang="ts" setup>
import { defineAsyncComponent, ref, h, type Slots } from 'vue'
import { MButton } from '@maas/mirror/vue'
import { useMagicToast } from '@maas/vue-equipment/plugins/MagicToast'
const count = ref(0)
const component = defineAsyncComponent(
() => import('./components/DemoToast.vue')
)
const slot = defineAsyncComponent(() => import('./components/DemoSlot.vue'))
const { add } = useMagicToast('magic-toast-nested-slot-demo')
function onClick() {
count.value += 1
// Break reactivity for count
const mappedCount = count.value
const slots: Slots = {
default: () => [h(slot, { message: `Component #${mappedCount}` })],
}
add({ component, slots })
}
</script>