Customize the tour
Theme the default card or replace its content without losing accessibility.
Customize the tour
The default theme is intentionally useful without configuration. It follows the
system color scheme and recognizes .light, .dark,
[data-theme="light"], and [data-theme="dark"].
Theme variables
Override variables on the tour root from application CSS:
[data-tour-part='root'] {
--tour-accent: #0f766e;
--tour-accent-hover: #115e59;
--tour-card-radius: 1rem;
--tour-arrow-size: 1rem;
--tour-overlay-color: rgb(2 6 23 / 64%);
--tour-spotlight-padding: 10px;
}Import structure.css instead of style.css when your design system owns all
visual rules. Set nuxtTour.css to false when application CSS imports an
entry itself.
Replace one section
Use #actions to supply application buttons while keeping the default title,
content, progress, and close control. Use #progress to change only progress.
Both slots receive the card context and resolved labels.
<script setup lang="ts">
const error = ref('')
async function advance(next: () => Promise<void>) {
error.value = ''
try {
await next()
}
catch (cause) {
error.value = cause instanceof Error ? cause.message : 'The next step could not open.'
}
}
</script>
<template>
<p v-if="error" role="alert">{{ error }}</p>
<TourHost>
<template #progress="{ index, total, labels }">
<p>{{ labels.progress(index + 1, total) }}</p>
</template>
<template #actions="{ controller, pending, index, total, labels }">
<button :disabled="pending" @click="advance(controller.next)">
{{ index === total - 1 ? labels.finish : labels.next }}
</button>
</template>
</TourHost>
</template>This example uses Nuxt auto-imports. In Vue, import ref from vue and
TourHost from @lupinum/nuxt-tour/vue. Your actions control their pending
presentation and error feedback. Style your own buttons; the built-in button
theme applies only to default actions. The default close control and Escape
remain available.
The #card slot takes precedence when supplied. Section slots are not rendered
inside a custom whole card.
Replace card content
<script setup lang="ts">
import { ref } from 'vue'
import { TourContent, TourHost } from '@lupinum/nuxt-tour/vue'
const error = ref('')
async function advance(next: () => Promise<void>) {
error.value = ''
try { await next() }
catch (cause) {
error.value = cause instanceof Error ? cause.message : 'The next step could not open.'
}
}
</script>
<template>
<p v-if="error" role="alert">{{ error }}</p>
<TourHost>
<template #card="{ step, title, controller, titleId, descriptionId, pending }">
<h2 v-if="title" :id="titleId">{{ title }}</h2>
<div :id="descriptionId"><TourContent :step="step" /></div>
<button :disabled="pending" @click="advance(controller.next)">Continue</button>
</template>
</TourHost>
</template>Import TourContent from @lupinum/nuxt-tour/vue. The host keeps ownership of
the dialog wrapper, name fallback, focus scope, Escape, positioning, and
cleanup. Use the supplied IDs so custom content remains correctly described.
Choose motion once
Motion is automatic by default. An eligible opening moves between nearby visible
targets when the requested scroll alignment needs no movement. A real scroll or
route change keeps a covered handoff. scroll: false controls scrolling only.
Disable all tour-owned animation and smooth scrolling in Nuxt:
export default defineNuxtConfig({
modules: ['@lupinum/nuxt-tour'],
nuxtTour: { motion: 'none' },
})In Vue, set the same option on createTourPlugin({ tours: [onboarding], motion: 'none' }). The default is motion: 'auto', which respects the operating system's
reduced-motion preference. The policy does not change animations inside your own
content components or elsewhere in the application.
Set clear spacing
Use gap: 16 on a step for 16 CSS pixels of clear space beyond the padded
spotlight and arrow reach. The default is 12. Existing offset values retain
their raw-target-to-card meaning. Use one option; specifying both is an error.
Placement may change to keep the card visible, and a modal card may overlap an
oversized target when the viewport leaves no external space.
Translate step names reactively
Control labels and step labels belong to the host. The optional labels.step
callback reads reactive state and returns translated heading and dialog labels.
The installed definition and controller's currentStep remain unchanged.
<script setup lang="ts">
import { ref } from 'vue'
import { TourHost } from '@lupinum/nuxt-tour/vue'
import type { TourLabels } from '@lupinum/nuxt-tour/vue'
const language = ref<'en' | 'de'>('en')
const labels: Partial<TourLabels> = {
step: ({ tourId, step }) => {
if (tourId !== 'onboarding' || step.id !== 'welcome') return {}
return { title: language.value === 'de' ? 'Willkommen' : 'Welcome' }
},
}
</script>
<template>
<TourHost :labels="labels" />
</template>In an i18n application, return values from its translation function in the same
callback. Translate ariaLabel too when the definition supplies a separate
accessible name. Omitted or blank translations retain the definition's validated
labels. A callback error is reported through the browser's error reporting and
retains those fallback labels. Custom slots receive tourId, resolved title,
and resolved ariaLabel; use title for a translated custom heading.
Keep command error feedback outside the host. A fatal navigation error closes the tour before rejecting, so an error inside a card slot would disappear.