Skip to content

Custom UI with Slots

Build completely custom step interfaces using Vue slots.

Default Slot

The VOnboardingWrapper provides a default slot with full access to step data and navigation:

vue
<template>
  <VOnboardingWrapper ref="wrapper" :steps="steps">
    <template #default="{ step, next, previous, exit, isFirst, isLast, index }">
      <!-- Your custom UI here -->
    </template>
  </VOnboardingWrapper>
</template>

Slot Props

PropTypeDescription
stepStepCurrent step configuration
next() => voidGo to next step (or finish if last)
previous() => voidGo to previous step
exit() => voidExit the tour
isFirstbooleanIs this the first step?
isLastbooleanIs this the last step?
indexnumberCurrent step index (0-based)

Using VOnboardingStep

Important

When using custom slot content, wrap it with VOnboardingStep to maintain proper positioning and overlay functionality.

vue
<template>
  <VOnboardingWrapper ref="wrapper" :steps="steps">
    <template #default="{ step, next, previous, exit, isFirst, isLast, index }">
      <VOnboardingStep>
        <!-- Custom content goes inside VOnboardingStep -->
        <div class="my-custom-tooltip">
          <h3>{{ step.content?.title }}</h3>
          <p>{{ step.content?.description }}</p>
          <button @click="next">
            {{ isLast ? 'Finish' : 'Next' }}
          </button>
        </div>
      </VOnboardingStep>
    </template>
  </VOnboardingWrapper>
</template>

<script setup>
import { VOnboardingWrapper, VOnboardingStep } from 'v-onboarding'
</script>

Conditional Custom UI

Show custom UI for specific steps only:

vue
<template>
  <VOnboardingWrapper ref="wrapper" :steps="steps">
    <template #default="{ step, next, previous, exit, isFirst, isLast, index }">
      <!-- Custom UI for step 3 -->
      <VOnboardingStep v-if="index === 2">
        <MySpecialTooltip
          :title="step.content?.title"
          @next="next"
          @close="exit"
        />
      </VOnboardingStep>

      <!-- Default UI for other steps -->
      <VOnboardingStep v-else />
    </template>
  </VOnboardingWrapper>
</template>

Complete Custom Example

Here's a full example with a completely custom design:

vue
<template>
  <VOnboardingWrapper ref="wrapper" :steps="steps">
    <template #default="{ step, next, previous, exit, isFirst, isLast, index }">
      <VOnboardingStep>
        <div class="custom-tooltip">
          <!-- Header -->
          <div class="tooltip-header">
            <span class="step-badge">Step {{ index + 1 }}</span>
            <button @click="exit" class="close-btn">×</button>
          </div>

          <!-- Content -->
          <h3 class="tooltip-title">{{ step.content?.title }}</h3>
          <p class="tooltip-description">{{ step.content?.description }}</p>

          <!-- Progress -->
          <div class="progress-dots">
            <span
              v-for="(_, i) in steps"
              :key="i"
              :class="['dot', { active: i === index }]"
            />
          </div>

          <!-- Actions -->
          <div class="tooltip-actions">
            <button v-if="!isFirst" @click="previous" class="btn-secondary">
              Back
            </button>
            <button @click="next" class="btn-primary">
              {{ isLast ? 'Done' : 'Continue' }}
            </button>
          </div>
        </div>
      </VOnboardingStep>
    </template>
  </VOnboardingWrapper>
</template>

<style scoped>
.custom-tooltip {
  background: white;
  border-radius: 12px;
  padding: 24px;
  max-width: 320px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
}

.tooltip-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.step-badge {
  background: #d4ff00;
  color: #000;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
}

.close-btn {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #999;
}

.tooltip-title {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 8px;
}

.tooltip-description {
  color: #666;
  font-size: 14px;
  line-height: 1.5;
}

.progress-dots {
  display: flex;
  gap: 6px;
  margin: 16px 0;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #ddd;
}

.dot.active {
  background: #d4ff00;
}

.tooltip-actions {
  display: flex;
  gap: 12px;
  margin-top: 20px;
}

.btn-primary {
  flex: 1;
  padding: 10px 20px;
  background: #d4ff00;
  border: none;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
}

.btn-secondary {
  padding: 10px 20px;
  background: transparent;
  border: 1px solid #ddd;
  border-radius: 8px;
  cursor: pointer;
}
</style>

Hiding the Arrow

For custom designs that don't need the pointer arrow:

css
/* Hide arrow for custom tooltip */
.custom-tooltip ~ [data-popper-arrow] {
  display: none;
}

Or style it to match your design:

css
.custom-tooltip ~ [data-popper-arrow]::before {
  background: white !important;
  border: none !important;
}

Released under the MIT License.