Rich content and images
Put images, video, live state, and application components inside a tour step.
Add rich content and images
A step accepts text or a Vue component. Use a component for images, video, links, live state, or product-specific layout. Nuxt Tour keeps ownership of the card, positioning, focus, and actions.
Create the content component
<!-- app/components/tours/ProjectOverview.vue -->
<template>
<figure class="tour-media">
<img
src="/images/project-overview.webp"
alt="Project overview with three active work areas"
width="640"
height="360"
/>
<figcaption>
Review progress and open work from one place.
</figcaption>
</figure>
</template>
<style scoped>
.tour-media {
display: grid;
gap: 0.75rem;
margin: 0;
}
.tour-media img {
width: 100%;
height: auto;
border-radius: 0.75rem;
}
</style>Always set the image dimensions. This prevents layout movement when the image
loads. Use useful alternative text when the image contains information. Use an
empty alt value when the image is decorative.
Use the component in a definition
// app/tours/onboarding.ts
import ProjectOverview from '~/components/tours/ProjectOverview.vue'
export default defineTour({
id: 'onboarding',
steps: [{
id: 'overview',
target: 'project-overview',
title: 'Your project overview',
content: ProjectOverview,
placement: 'bottom',
}],
})The component can use stores, composables, translations, and normal Vue state. Do not pass raw HTML strings. A component keeps rendering typed and safe.
Use live application state
<script setup lang="ts">
const project = useCurrentProject()
</script>
<template>
<p>
{{ project.completedTasks }} of {{ project.totalTasks }} tasks are complete.
</p>
</template>Keep the content focused. A tour card is a short guide, not a replacement for a product page.
Load large content when the step opens
Tour discovery loads definitions eagerly. Keep large content components in
separate chunks with Vue's defineAsyncComponent. This keeps typed tour IDs and
synchronous progress totals without a second tour-loading API.
import { defineAsyncComponent, defineComponent, h } from 'vue'
const Report = defineAsyncComponent({
loader: () => import('~/components/tours/Report.vue'),
loadingComponent: defineComponent(() => () => h('p', 'Loading the report…')),
errorComponent: defineComponent(() => () => h('p', { role: 'alert' }, 'The report could not load. Close the tour and try again.')),
delay: 150,
timeout: 10000,
})
export default defineTour({
id: 'report',
steps: [{ id: 'overview', title: 'Your report', content: Report }],
})Create app/components/tours/Report.vue for this Nuxt example. In Vue, use a
relative component import and import defineTour from
@lupinum/nuxt-tour/vue. Install the definition through createTourPlugin.
Component loading is separate from step preparation: the card can open with its
loading content. The host repositions when the content changes size. If an
operation must finish before a target can be resolved, use prepare() instead.
The application owns content loading errors and retry behavior.