Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

OverlaysReact

Tour

A guided tour of a page: a popup that walks through steps, one at a time, each anchored to the element it explains.

Rendered by React 19.3.1

Loading React…
import * as React from 'react'
import { Tour, type TourStep } from '@hamur/react/tour'

interface Copy {
  title: string
  body: string
}

function XIcon() {
  return (
    <svg className="tour-icon" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M4 4l8 8M12 4l-8 8" />
    </svg>
  )
}

export default function TourExample() {
  const newFile = React.useRef<HTMLButtonElement>(null)
  const search = React.useRef<HTMLInputElement>(null)
  const settings = React.useRef<HTMLElement>(null)
  const steps = React.useMemo<TourStep<Copy>[]>(
    () => [
      {
        id: 'new-file',
        target: newFile,
        data: { title: 'Create a file', body: 'Start a blank document from the toolbar.' },
      },
      {
        id: 'search',
        target: search,
        data: { title: 'Find anything', body: 'Search file names and their contents.' },
      },
      {
        id: 'settings',
        target: settings,
        side: 'top',
        data: { title: 'Your settings', body: 'Turn autosave and sharing on or off here.' },
      },
    ],
    [],
  )
  return (
    <div className="tour-page">
      <div className="tour-toolbar">
        <button ref={newFile} type="button" className="button">
          New file
        </button>
        <input
          ref={search}
          className="input tour-search"
          aria-label="Search files"
          placeholder="Search files"
        />
      </div>
      <section ref={settings} className="tour-settings" aria-label="Settings">
        <label className="row">
          <input type="checkbox" defaultChecked />
          Autosave
        </label>
        <label className="row">
          <input type="checkbox" />
          Share with my team
        </label>
      </section>
      <Tour.Root steps={steps}>
        {({ step }) => (
          <>
            <Tour.Trigger className="button">
              Take the tour
            </Tour.Trigger>
            <Tour.Portal>
              <Tour.Backdrop className="backdrop" />
              <Tour.Spotlight className="tour-spotlight" />
              <Tour.Positioner className="tour-positioner">
                <Tour.Popup className="popover tour-popup">
                  <Tour.Arrow className="arrow" />
                  <div className="tour-head">
                    <Tour.Progress className="tour-progress" />
                    <Tour.Close className="tour-close" aria-label="Close tour">
                      <XIcon />
                    </Tour.Close>
                  </div>
                  <Tour.Title className="popover-title">
                    {step?.data?.title}
                  </Tour.Title>
                  <Tour.Description className="muted">
                    {step?.data?.body}
                  </Tour.Description>
                  <div className="tour-footer">
                    <Tour.Skip className="tour-skip" />
                    <Tour.Previous className="button" />
                    <Tour.Next className="button" />
                  </div>
                </Tour.Popup>
              </Tour.Positioner>
            </Tour.Portal>
          </>
        )}
      </Tour.Root>
    </div>
  )
}
Not measured yetTests passing in React
Not measured yetSize
Not measured yetMemory
Not measured yetSpeed
[N] commits

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<Tour.Root>
  <Tour.Trigger />
  <Tour.Portal>
    <Tour.Backdrop />
    <Tour.Spotlight />
    <Tour.Positioner>
      <Tour.Popup>
        <Tour.Arrow />
        <Tour.Progress />
        <Tour.Close />
        <Tour.Title />
        <Tour.Description />
        <Tour.Skip />
        <Tour.Previous />
        <Tour.Next />
      </Tour.Popup>
    </Tour.Positioner>
  </Tour.Portal>
</Tour.Root>

Examples

Centered

A welcome step with a null target, centered with no Arrow and no hole, then steps anchored to the page.

TourCentered.tsx
import * as React from 'react'
import { Tour, type TourStep } from '@hamur/react/tour'

interface Copy {
  title: string
  body: string
}

function XIcon() {
  return (
    <svg className="tour-icon" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M4 4l8 8M12 4l-8 8" />
    </svg>
  )
}

export default function TourCenteredExample() {
  const upload = React.useRef<HTMLButtonElement>(null)
  const filter = React.useRef<HTMLInputElement>(null)
  const steps = React.useMemo<TourStep<Copy>[]>(
    () => [
      {
        id: 'welcome',
        target: null,
        data: { title: 'Welcome to Files', body: 'Three steps show you where things are.' },
      },
      {
        id: 'upload',
        target: upload,
        data: { title: 'Upload files', body: 'Add files from your computer.' },
      },
      {
        id: 'filter',
        target: filter,
        data: { title: 'Filter the list', body: 'Type part of a name to narrow the list.' },
      },
    ],
    [],
  )
  return (
    <div className="tour-page">
      <div className="tour-toolbar">
        <button ref={upload} type="button" className="button">
          Upload
        </button>
        <input
          ref={filter}
          className="input tour-search"
          aria-label="Filter files"
          placeholder="Filter files"
        />
      </div>
      <Tour.Root steps={steps}>
        {({ step }) => (
          <>
            <Tour.Trigger className="button">Start the tour</Tour.Trigger>
            <Tour.Portal>
              <Tour.Backdrop className="backdrop" />
              <Tour.Spotlight className="tour-spotlight" />
              <Tour.Positioner className="tour-positioner">
                <Tour.Popup className="popover tour-popup">
                  <Tour.Arrow className="arrow" />
                  <div className="tour-head">
                    <Tour.Progress className="tour-progress" />
                    <Tour.Close className="tour-close" aria-label="Close tour">
                      <XIcon />
                    </Tour.Close>
                  </div>
                  <Tour.Title className="popover-title">{step?.data?.title}</Tour.Title>
                  <Tour.Description className="muted">{step?.data?.body}</Tour.Description>
                  <div className="tour-footer">
                    <Tour.Previous className="button" />
                    <Tour.Next className="button" />
                  </div>
                </Tour.Popup>
              </Tour.Positioner>
            </Tour.Portal>
          </>
        )}
      </Tour.Root>
    </div>
  )
}

Controlled

open and step held by the page, with its own list of the steps and a Restart button that calls actionsRef; modal is off.

TourControlled.tsx
import * as React from 'react'
import { Tour, type TourStep } from '@hamur/react/tour'

interface Copy {
  title: string
  body: string
}

function XIcon() {
  return (
    <svg className="tour-icon" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M4 4l8 8M12 4l-8 8" />
    </svg>
  )
}

export default function TourControlledExample() {
  const [open, setOpen] = React.useState(false)
  const [step, setStep] = React.useState('compose')
  const actionsRef = React.useRef<Tour.Root.Actions>(null)
  const compose = React.useRef<HTMLButtonElement>(null)
  const labels = React.useRef<HTMLButtonElement>(null)
  const archive = React.useRef<HTMLButtonElement>(null)
  const steps = React.useMemo<TourStep<Copy>[]>(
    () => [
      {
        id: 'compose',
        target: compose,
        data: { title: 'Compose', body: 'Write a new message.' },
      },
      {
        id: 'labels',
        target: labels,
        data: { title: 'Labels', body: 'Sort messages under labels of your own.' },
      },
      {
        id: 'archive',
        target: archive,
        data: { title: 'Archive', body: 'Clear the inbox without deleting anything.' },
      },
    ],
    [],
  )
  return (
    <div className="tour-page">
      <ol className="tour-steps" aria-label="Tour steps">
        {steps.map((item) => (
          <li key={item.id}>
            <button
              type="button"
              className="tour-step"
              aria-current={item.id === step ? 'step' : undefined}
              onClick={() => {
                setStep(item.id)
                setOpen(true)
              }}
            >
              {item.data?.title}
            </button>
          </li>
        ))}
      </ol>
      <div className="tour-toolbar">
        <button
          type="button"
          className="button"
          onClick={() => actionsRef.current?.start('compose')}
        >
          Restart
        </button>
        <p className="muted">
          {open ? 'Open' : 'Closed'}. Step: {step}.
        </p>
      </div>
      <div className="tour-toolbar">
        <button ref={compose} type="button" className="button">
          Compose
        </button>
        <button ref={labels} type="button" className="button">
          Labels
        </button>
        <button ref={archive} type="button" className="button">
          Archive
        </button>
      </div>
      <Tour.Root
        steps={steps}
        open={open}
        onOpenChange={setOpen}
        step={step}
        onStepChange={setStep}
        actionsRef={actionsRef}
        modal={false}
      >
        {({ step: shown }) => (
          <Tour.Portal>
            <Tour.Spotlight className="tour-spotlight" />
            <Tour.Positioner className="tour-positioner">
              <Tour.Popup className="popover tour-popup">
                <Tour.Arrow className="arrow" />
                <div className="tour-head">
                  <Tour.Progress className="tour-progress" />
                  <Tour.Close className="tour-close" aria-label="Close tour">
                    <XIcon />
                  </Tour.Close>
                </div>
                <Tour.Title className="popover-title">{shown?.data?.title}</Tour.Title>
                <Tour.Description className="muted">{shown?.data?.body}</Tour.Description>
                <div className="tour-footer">
                  <Tour.Previous className="button" />
                  <Tour.Next className="button" />
                </div>
              </Tour.Popup>
            </Tour.Positioner>
          </Tour.Portal>
        )}
      </Tour.Root>
    </div>
  )
}

Late Target

The second step's chart renders only after the report loads: the first step stays shown, aria-busy and data-resolving, until it appears.

TourLateTarget.tsx
import * as React from 'react'
import { Tour, type TourStep } from '@hamur/react/tour'

interface Copy {
  title: string
  body: string
}

const BARS = [40, 65, 50, 80, 95, 70]

function XIcon() {
  return (
    <svg className="tour-icon" viewBox="0 0 16 16" aria-hidden="true">
      <path d="M4 4l8 8M12 4l-8 8" />
    </svg>
  )
}

export default function TourLateTargetExample() {
  const [report, setReport] = React.useState<'none' | 'loading' | 'loaded'>('none')
  const load = React.useRef<HTMLButtonElement>(null)
  const chart = React.useRef<HTMLDivElement>(null)
  const steps = React.useMemo<TourStep<Copy>[]>(
    () => [
      {
        id: 'load',
        target: load,
        data: {
          title: 'Load a report',
          body: 'Next loads the report; the tour waits for its chart.',
        },
      },
      {
        id: 'chart',
        target: chart,
        data: { title: 'Revenue by month', body: 'Each bar is one month of the last half year.' },
      },
    ],
    [],
  )

  React.useEffect(() => {
    if (report !== 'loading') return undefined
    const timeout = setTimeout(() => setReport('loaded'), 1500)
    return () => clearTimeout(timeout)
  }, [report])

  return (
    <div className="tour-page">
      <div className="tour-toolbar">
        <button
          ref={load}
          type="button"
          className="button"
          onClick={() => setReport((current) => (current === 'none' ? 'loading' : current))}
        >
          Load report
        </button>
      </div>
      {report === 'loaded' ? (
        <div ref={chart} className="tour-chart" role="img" aria-label="Revenue by month">
          {BARS.map((height, index) => (
            <span key={index} className="tour-bar" style={{ height: `${height}%` }} />
          ))}
        </div>
      ) : (
        <p className="muted">{report === 'loading' ? 'Loading the chart…' : 'No report loaded.'}</p>
      )}
      <Tour.Root
        steps={steps}
        onOpenChange={(open) => {
          if (open) setReport('none')
        }}
        onStepChange={(id) => {
          if (id === 'chart') setReport((current) => (current === 'none' ? 'loading' : current))
        }}
      >
        {({ step }) => (
          <>
            <Tour.Trigger className="button">Show me around</Tour.Trigger>
            <Tour.Portal>
              <Tour.Backdrop className="backdrop" />
              <Tour.Spotlight className="tour-spotlight" />
              <Tour.Positioner className="tour-positioner">
                <Tour.Popup className="popover tour-popup">
                  <Tour.Arrow className="arrow" />
                  <div className="tour-head">
                    <Tour.Progress className="tour-progress" />
                    <Tour.Close className="tour-close" aria-label="Close tour">
                      <XIcon />
                    </Tour.Close>
                  </div>
                  <Tour.Title className="popover-title">{step?.data?.title}</Tour.Title>
                  <Tour.Description className="muted">{step?.data?.body}</Tour.Description>
                  <div className="tour-footer">
                    <Tour.Previous className="button" />
                    <Tour.Next className="button" />
                  </div>
                </Tour.Popup>
              </Tour.Positioner>
            </Tour.Portal>
          </>
        )}
      </Tour.Root>
    </div>
  )
}

API reference

45 props across 15 parts, in React syntax. className, style, render are listed once, under Shared props.

ArrowTour.Arrow

Displays an element positioned against the target. Not rendered for a centered step. Renders a <div>.

Arrow data attributes
Data attributeDescription
data-openPresent when the tour is open.
data-closedPresent when the tour is closed.
data-sideIndicates which side the popup is positioned relative to the target.
data-alignIndicates how the popup is aligned relative to specified side.
data-uncenteredPresent when the arrow is uncentered.

BackdropTour.Backdrop

The dim layer, with the hole cut out of it. Renders a <div>.

Backdrop data attributes
Data attributeDescription
data-openPresent when the tour is open.
data-closedPresent when the tour is closed.
data-starting-stylePresent when the backdrop is animating in.
data-ending-stylePresent when the backdrop is animating out.
data-centeredPresent when the shown step is centered: it has no target, or a zero-size or hidden one.
Backdrop CSS variables
CSS variableDescription
--spotlight-xThe x coordinate of the hole, in px.
--spotlight-yThe y coordinate of the hole, in px.
--spotlight-widthThe width of the hole, in px.
--spotlight-heightThe height of the hole, in px.
--spotlight-radiusThe four corner radii of the hole, as a border-radius value.

CloseTour.Close

A button that closes the tour. Renders a <button>.

Close props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue

DescriptionTour.Description

A paragraph that describes the tour's popup and the step's target. Renders a <p>.

NextTour.Next

A button that shows the next step. On the last step it finishes the tour. Renders a <button>.

Next props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
Next data attributes
Data attributeDescription
data-lastPresent on the last step, where the button finishes the tour.

PopupTour.Popup

A container for the step's contents. It is focused on open and on every step change. Renders a <div>.

Popup props
PropTypeDefault
initialFocusDetermines the element to focus on open and on every step change.boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType, info: { step: TourStep<any> | null; index: number; }) => boolean | void | HTMLElement | null)—
finalFocusDetermines the element to focus when the tour closes.boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)—
Popup data attributes
Data attributeDescription
data-openPresent when the tour is open.
data-closedPresent when the tour is closed.
data-starting-stylePresent when the popup begins animating in.
data-ending-stylePresent when the popup is animating out.
data-sideIndicates which side of the target the popup is positioned on. Absent on a centered step.
data-alignIndicates how the popup is aligned relative to the specified side. Absent on a centered step.
data-instantPresent if animations should be instant.
data-firstPresent on the first step.
data-lastPresent on the last step.
data-centeredPresent when the shown step is centered.
data-directionThe direction of the last step change. Absent on open.
data-resolvingPresent while the next step's target is being waited for.
Popup CSS variables
CSS variableDescription
--popup-widthThe width of the popup.
--popup-heightThe height of the popup.

PortalTour.Portal

A portal element that moves the tour's layers and popup to a different part of the DOM. By default, the portal element is appended to <body>. Renders a <div>.

Portal props
PropTypeDefault
keepMountedWhether to keep the portal mounted in the DOM while the tour is closed.booleanfalse
containerA parent element to render the portal element into.HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null—

PositionerTour.Positioner

Positions the popup against the shown step's target, or in the middle of the viewport for a centered step. Renders a <div>.

Positioner props
PropTypeDefault
positionMethodDetermines which CSS position property to use."absolute" | "fixed"'absolute'
sideWhich side of the anchor element to align the popup against.Side'bottom'
alignHow to align the popup relative to the specified side.Align'center'
alignOffsetAdditional offset along the alignment axis in pixels.number | OffsetFunction0
collisionBoundaryAn element or a rectangle that delimits the area that the popup is confined to.Boundary'clipping-ancestors'
collisionPaddingAdditional space to maintain from the edge of the collision boundary.Padding5
stickyWhether to maintain the popup in the viewport afterbooleanfalse
arrowPaddingMinimum distance to maintain between the arrow and the edges of the popup.number5
collisionAvoidanceDetermines how to handle collisions when positioning the popup.CollisionAvoidance—
sideOffsetDistance between the target and the popup in pixels.number | OffsetFunction12
Positioner data attributes
Data attributeDescription
data-openPresent when the tour is open.
data-closedPresent when the tour is closed.
data-anchor-hiddenPresent when the target is hidden.
data-sideIndicates which side of the target the popup is positioned on. Absent on a centered step.
data-alignIndicates how the popup is aligned relative to the specified side. Absent on a centered step.
data-centeredPresent when the shown step is centered.
data-instantPresent when the last move came from scroll, resize or layout rather than a step change.
Positioner CSS variables
CSS variableDescription
--available-widthThe available width between the target and the edge of the viewport.
--available-heightThe available height between the target and the edge of the viewport.
--anchor-widthThe anchor's width.
--anchor-heightThe anchor's height.
--transform-originThe coordinates that this element is anchored to. Used for animations and transitions.
--positioner-widthThe width of the tour's positioner. It is important to set width to this value when using CSS to animate size changes.
--positioner-heightThe height of the tour's positioner. It is important to set height to this value when using CSS to animate size changes.

PreviousTour.Previous

A button that shows the previous step. On the first step it is aria-disabled and stays focusable. Renders a <button>.

Previous props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
focusableWhenDisabledWhether the button stays focusable on the first step, where it is disabled.booleantrue
Previous data attributes
Data attributeDescription
data-disabledPresent on the first step, where the button does nothing.

ProgressTour.Progress

The shown step's position, "2 of 5" by default. Renders a <span>.

Progress data attributes
Data attributeDescription
data-firstPresent on the first step.
data-lastPresent on the last step.

RootTour.Root

Groups all parts of the tour. Doesn’t render its own element.

Root props
PropTypeDefault
stepsrequiredThe steps of the tour, in order.readonly TourStep<Data>[]—
defaultOpenWhether the tour is initially open.booleanfalse
openWhether the tour is currently open.boolean—
onOpenChangeEvent handler called when the tour is opened or closed.((open: boolean, eventDetails: TourRootChangeEventDetails) => void)—
onOpenChangeCompleteEvent handler called after any animations complete when the tour is opened or closed.((open: boolean) => void)—
stepThe id of the shown step.string—
defaultStepThe id of the step an uncontrolled tour starts at.string—
onStepChangeEvent handler called when a step is requested, before its target is looked up.((stepId: string, eventDetails: TourRootStepChangeEventDetails) => void)—
modalDetermines if the tour enters a modal state when open.boolean | "trap-focus"true
disablePointerDismissalWhether a press outside the popup, the target and the spotlight element is ignored.booleantrue
disableTargetInteractionWhether pointer interactions with the target through the hole are blocked.booleanfalse
keyboardNavigationWhether ArrowRight and ArrowLeft move to the next and previous steps.booleantrue
describeTargetWhether the shown target's aria-describedby gets the Description's id.booleantrue
spotlightPaddingThe space around the spotlight element in the hole, in px.number8
spotlightRadiusThe corner radius of the hole: a number, the corners of an object, or 'auto' to follow theTourSpotlightRadius'auto'
targetTimeoutHow long to wait for a step's target to appear, in ms.number3000
missingTargetWhat a step whose target is still missing at the timeout does: 'skip' shows the next step inTourMissingTarget'skip'
labelsThe texts of the parts.Partial<TourLabels>—
actionsRefA ref to imperative actions.React.RefObject<TourRootActions | null>—
handleA handle to associate the tour with detached triggers.TourHandle—
childrenThe content of the tour.React.ReactNode | ((args: TourChildRenderArgs<Data>) => React.ReactNode)—

SkipTour.Skip

A button that ends the tour for good, as opposed to closing it for now. Renders a <button>.

Skip props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue

SpotlightTour.Spotlight

A box at the hole, for a ring around the target. Not rendered while the step has no hole. Renders a <div>.

Spotlight data attributes
Data attributeDescription
data-openPresent when the tour is open.
data-closedPresent when the tour is closed.
data-starting-stylePresent when the spotlight is animating in.
data-ending-stylePresent when the spotlight is animating out.
data-instantPresent when the last move came from scroll, resize or layout rather than a step change.
Spotlight CSS variables
CSS variableDescription
--spotlight-xThe x coordinate of the hole, in px.
--spotlight-yThe y coordinate of the hole, in px.
--spotlight-widthThe width of the hole, in px.
--spotlight-heightThe height of the hole, in px.
--spotlight-radiusThe four corner radii of the hole, as a border-radius value.

TitleTour.Title

A heading that names the tour's popup. Renders a <h2>.

TriggerTour.Trigger

A button that starts the tour. Renders a <button>.

Trigger props
PropTypeDefault
nativeButtonWhether the component renders a native <button> element when replacing itbooleantrue
stepThe id of the step the tour starts at when this trigger opens it.string—
handleA handle to associate the trigger with a tour rendered elsewhere.TourHandle—
disabledWhether the trigger ignores user interaction.booleanfalse
idID of the trigger.string—
Trigger data attributes
Data attributeDescription
data-popup-openPresent while the tour is running.
data-disabledPresent when the trigger is disabled.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Keyboard
KeysDoesPart
ArrowRightArrowLeftMoves to the next or previous step, from the Popup or the targetPopup
ArrowRightArrowLeftSwapped in right-to-left textPopup
ArrowRightArrowLeftDoes nothing at the first and the last stepPopup
EscapeCloses the tour, also while a target is looked forPopup
TabMoves from the Popup to the target and back, while modalPopup
TabWith modal off, leaves the Popup for what follows the targetPopup
EnterSpaceOpens the tour, at its first step or at the Trigger's stepTrigger
EnterSpaceMoves to the next step; on the last step reads "Done" and closesNext
EnterSpaceMoves to the previous step; on the first step it is disabled and focusablePrevious
EnterSpaceEnds the tour, with the reason skip-press or close-pressSkip

Accessibility

What the parts set
PartSetsSo that
Triggeraria-haspopup="dialog", and no aria-expandeda reader hears that a dialog opens, and nothing claims the button controls the page
Popuprole="dialog", tabindex="-1", named by the Title (or "Step 2 of 3" without one), described by the Description; focused on each stepeach step is announced when it is shown, its title first
Popuparia-busy="true" while the target is looked fora reader is not sent to a step whose element is not there yet
Descriptionits id added to the target's aria-describedby, beside the target's own, and removed on the next stepthe target, once reached with Tab, is read with what the step says about it
Backdroparia-hidden="true", role="presentation"the dim layer is never read
Spotlightaria-hidden="true", pointer-events: nonethe ring around the target is decoration only
Arrowaria-hidden="true"; not rendered for a centered stepthe arrow is decoration, and a centered step points at nothing
Progresstext only, "2 of 3", no rolethe count is read with the Popup, not announced on every change
Previousaria-disabled on the first step, still focusablethe button keeps its place in the Tab order and says why it does nothing
Rootwith modal, every element outside the Popup, the target and the spotlight element hidden from assistive technology, restored on closea reader stays in the tour and on the element it explains

Focus order

  1. The Popup itself, focused when the tour opens and after each step change.
  2. Then its controls in order: Close, Skip, Previous and Next in the examples.
  3. Then, with modal, the target and anything focusable in it, and back to the Popup; with modal off, Tab goes on to what follows the target.