docs-src/src/content/docs/recipes/cookbook.md
Previously, disabling scrolling was built into Shepherd, but it was buggy
and bulky, so we opted to remove body-scroll-lock
as a dependency, in favor of users installing it directly in their apps. To disable scrolling,
you can install body-scroll-lock and run bodyScrollLock.disableBodyScroll(); before
starting the tour, then bodyScrollLock.clearAllBodyScrollLocks(); after stopping the tour.
Highlighting multiple elements is supported by Shepherd out of the box. You can pass an array of selectors to the extraHighlights option in the step configuration. This will highlight all the elements in the array as well as the target element defined in the attachTo option.
const tour = new Shepherd.Tour({
steps: [
{
text: 'This is a step with multiple highlights',
attachTo: {
element: '.target-element',
on: 'bottom'
},
extraHighlights: ['.example-selector', '.example-selector-2']
}
]
});
If an element to be highlighted is contained by another element that is also being highlighted, the contained element will not be highlighted. This is to prevent the contained element from being obscured by the containing element.
Highlighted elements do not have to share a scroll container with the attachTo target. Each one is clipped vertically by the scroll containers that actually crop it, so only the part of it that is scrolled into view is cut out of the overlay. An element whose position takes it outside those containers — fixed, or absolute against a containing block above them, as a dropdown usually is — is cut out in full, wherever it is painted. Clipping is vertical only: an element scrolled out of view horizontally is still cut out in full.
By default, FloatingUI instances are placed directly next to their target. However, if you need to apply some margin between them or if you need to fine tune the position according to some custom logic, you can use an offset middleware.
For example:
import { offset } from '@floating-ui/dom';
const tour = new Shepherd.Tour({
steps: [
{
...
floatingUIOptions: {
middleware: [offset({ mainAxis: 0, crossAxis: 12 })]
}
...
}
]
});
Steps are positioned with position: absolute by default. Shepherd repositions
them through Floating UI's autoUpdate, so the default already keeps a step
locked to its target while the page — or any scrolling ancestor of the target —
scrolls.
If you need the step element to be position: fixed instead, set the Floating
UI strategy. Floating UI recommends this when the target itself is
position: fixed, or to escape a clipping ancestor; see
its strategy documentation
for the trade-offs.
For example:
const tour = new Shepherd.Tour({
steps: [
{
...
floatingUIOptions: {
strategy: 'fixed'
}
...
}
]
});
You can also set this once for every step via defaultStepOptions:
const tour = new Shepherd.Tour({
defaultStepOptions: {
floatingUIOptions: {
strategy: 'fixed'
}
}
});
Centered steps are always positioned in the viewport with position: fixed, so
strategy has no effect on them. A step is centered when it has no attachTo
at all, or when its attachTo is missing either element or on.
Using the already exposed API, you could add a progress indicator of your choosing for each step to let your users know how far into a tour they may be.
The example below uses the Step options
object and adds to when on the show event. Within that, we create an element
to render in the header with text of what step out of all potential steps is now
being show.
when: {
show() {
const currentStep = Shepherd.activeTour?.getCurrentStep();
const currentStepElement = currentStep?.getElement();
const header = currentStepElement?.querySelector('.shepherd-header');
const progress = document.createElement('span');
progress.style['margin-right'] = '315px';
progress.innerText = `${Shepherd.activeTour?.steps.indexOf(currentStep) + 1}/${Shepherd.activeTour?.steps.length}`;
header?.insertBefore(progress, currentStepElement.querySelector('.shepherd-cancel-icon'));
}
}
Another example, for anyone who wants to add progress indicators to the footer. Add the shepherd-progress className and some extra styles.
when: {
show() {
const currentStep = Shepherd.activeTour?.getCurrentStep();
const currentStepElement = currentStep?.getElement();
const footer = currentStepElement?.querySelector('.shepherd-footer');
const progress = document.createElement('span');
progress.className = 'shepherd-progress';
progress.innerText = `${Shepherd.activeTour?.steps.indexOf(currentStep) + 1} of ${Shepherd.activeTour?.steps.length}`;
footer?.insertBefore(progress, currentStepElement.querySelector('.shepherd-button:last-child'));
}
}
.shepherd-footer {
align-items: center;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
display: flex;
justify-content: space-between;
padding: 0 0.75rem 0.75rem;
.shepherd-button:last-child {
margin-right: 0;
}
.shepherd-progress {
font-size: 0.8rem;
}
}