npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

reguide

v0.5.0

Published

A React library for guided tours

Readme

reguide

Guided tours for React apps with spotlight targeting, step-by-step flow control, and configurable UI.

What You Get

  • Spotlight highlight around the active target element
  • Floating card with Back, Next, and Close controls
  • Step progression modes (default, click, interact, custom)
  • Optional section steps (multi-step groups under one top-level title)
  • Optional progress persistence by key
  • Simple imperative API through useReguide

Requirements

  • React ^18.0.0 or ^19.0.0
  • React DOM ^18.0.0 or ^19.0.0

Install

pnpm add reguide react react-dom

Import styles once, usually in your app entry file:

import 'reguide/style.css'

Quick Start

This example wires a 3-step tour with typed step ids and includes Start/Stop controls.

import { createReguideContext, type ReguideStep } from 'reguide'
import 'reguide/style.css'

const STEP_IDS = {
  PROFILE: 'PROFILE',
  SEARCH: 'SEARCH',
  PUBLISH: 'PUBLISH',
} as const

type StepId = keyof typeof STEP_IDS

const reguide = createReguideContext<StepId>()
const { Provider, useReguide, useTarget } = reguide

function TourControls() {
  const start = useReguide((state) => state.start)
  const prev = useReguide((state) => state.prev)
  const next = useReguide((state) => state.next)
  const stop = useReguide((state) => state.stop)
  const canGoPrev = useReguide((state) => state.canGoPrev)
  const canGoNext = useReguide((state) => state.canGoNext)

  return (
    <div style={{ display: 'flex', gap: 8 }}>
      <button type='button' onClick={start}>Start tour</button>
      <button type='button' onClick={prev} disabled={!canGoPrev}>Back</button>
      <button type='button' onClick={next} disabled={!canGoNext}>Next</button>
      <button type='button' onClick={stop}>Close</button>
    </div>
  )
}

function ProfileButton() {
  const ref = useTarget<HTMLButtonElement>(STEP_IDS.PROFILE)
  return <button ref={ref} type='button'>Profile</button>
}

function SearchInput() {
  const ref = useTarget<HTMLInputElement>(STEP_IDS.SEARCH)
  return <input ref={ref} aria-label='Search' />
}

function PublishButton() {
  const ref = useTarget<HTMLButtonElement>(STEP_IDS.PUBLISH)
  return <button ref={ref} type='button'>Publish</button>
}

export function App() {
  const steps: ReguideStep<StepId>[] = [
    {
      id: STEP_IDS.PROFILE,
      title: 'Open your profile menu',
      body: 'Click this button to continue.',
      mode: 'click',
    },
    {
      id: STEP_IDS.SEARCH,
      title: 'Try search',
      body: 'Type in the input to enable Next.',
      mode: 'interact',
      autoFocus: true,
    },
    {
      id: STEP_IDS.PUBLISH,
      title: 'Publish your first item',
      body: 'Final step. Click Next to close.',
    },
  ]

  return (
    <Provider steps={steps}>
      <ProfileButton />
      <SearchInput />
      <PublishButton />
      <TourControls />
    </Provider>
  )
}

Core Concepts

Step Types

ReguideStep can be one of these:

  • Standard step: title, body, optional targetRef or id
  • Custom step: standard step + mode: 'custom' + validator
  • Section step: top-level title + steps (children use subtitle + body)

Progression Modes

  • default: Next is enabled immediately.
  • click: Clicking the target advances to the next step.
  • interact: Next stays disabled until click/input/keydown occurs on the target.
  • custom: Next stays disabled until your validator returns true.

Custom validator example:

const hasSavedRef = useRef(false)
const saveButtonRef = useRef<HTMLButtonElement | null>(null)

const steps: ReguideStep[] = [
  {
    title: 'Save your settings',
    body: 'Click Save to continue.',
    targetRef: saveButtonRef,
    mode: 'custom',
    progressOnValidate: true,
    validator: ({ eventType }) => eventType === 'click' && hasSavedRef.current,
  },
]

<button
  ref={saveButtonRef}
  type='button'
  onClick={() => {
    hasSavedRef.current = true
  }}
>
  Save
</button>

Section Steps

Use section steps when a top-level stage has multiple sub-steps.

const dataSourceRef = useRef<HTMLButtonElement | null>(null)
const reportNameRef = useRef<HTMLInputElement | null>(null)

const steps: ReguideStep[] = [
  {
    title: 'Create your first report',
    steps: [
      {
        subtitle: 'Pick a data source',
        body: 'Select any source to continue.',
        targetRef: dataSourceRef,
        mode: 'interact',
      },
      {
        subtitle: 'Name the report',
        body: 'Use a clear name so teammates can find it.',
        targetRef: reportNameRef,
      },
    ],
  },
]

In a section, the UI shows a Skip section action. It is disabled when there is no following top-level step.

Target Binding

Option 1: Direct refs on steps

Pass targetRef directly in each step. This is the simplest approach.

Option 2: Step ids + target registration

Use id on steps and register targets independently. This helps when step definitions live in a different module from UI components.

Factory example (define once, export, and consume in app components):

// reguide-targets.ts
import { createReguideContext } from 'reguide'

type StepId = 'profile' | 'search'
export const reguide = createReguideContext<StepId>()
// App.tsx
import { type ReguideStep } from 'reguide'
import { reguide } from './reguide-targets'

const { Provider, useTarget } = reguide

function ProfileButton() {
  const ref = useTarget<HTMLButtonElement>('profile')
  return <button ref={ref} type='button'>Profile</button>
}

function SearchInput() {
  const ref = useTarget<HTMLInputElement>('search')
  return <input ref={ref} aria-label='Search' />
}

const steps: ReguideStep<'profile' | 'search'>[] = [
  {
    id: 'profile',
    title: 'Open profile menu',
    body: 'Click to continue.',
    mode: 'click',
  },
  {
    id: 'search',
    title: 'Use search',
    body: 'Type any text.',
    mode: 'interact',
  },
]

export function App() {
  return (
    <Provider steps={steps}>
      <ProfileButton />
      <SearchInput />
    </Provider>
  )
}

If both targetRef and id registration are available for a step, targetRef wins.

ReguideProvider Reference

<ReguideProvider steps={steps} initialOpen={false}>
  {children}
</ReguideProvider>

Props:

  • steps: ReguideStep[] (required)
  • initialOpen?: boolean
  • theme?: ReguideTheme
  • buttonText?: ReguideButtonText
  • persistence?: ReguidePersistenceOptions
  • onStart?: () => void | Promise<void>
  • onStop?: (event: ReguideStopEvent) => void | Promise<void>
  • onStepChange?: (event: ReguideStepChangeEvent) => void | Promise<void>

Persistence

Persist progress and restore it later:

<ReguideProvider
  steps={steps}
  persistence={{
    key: 'my-app:onboarding',
    persistIsOpen: true,
  }}
>
  {children}
</ReguideProvider>

The persisted state stores step id when available, with step index fallback.

Lifecycle callbacks

<ReguideProvider
  steps={steps}
  onStart={() => track('guide_started')}
  onStop={(event) => track('guide_stopped', event)}
  onStepChange={(event) => track('guide_step_changed', event)}
>
  {children}
</ReguideProvider>

onStepChange includes:

  • source (start, next, prev, goToStep, goToStepById, skipSection, click, custom-auto, restore)
  • currentStepIndex, currentStepId
  • previousStepIndex, previousStepId

useReguide Reference

useReguide() returns:

  • isOpen
  • steps
  • currentStepIndex (top-level step index)
  • currentLeafStepIndex (rendered step index, including section children)
  • currentStep
  • interactionSatisfied
  • canGoNext
  • canGoPrev
  • start(): Promise<void>
  • stop(): Promise<void>
  • next(): Promise<void>
  • prev(): Promise<void>
  • goToStep(index: number): Promise<void>
  • goToStepById(id: string): Promise<void>

useReguide(selector) subscribes only to the selected slice. Use this for action-only and read-only components that should avoid rerendering on unrelated guide updates.

Notes

  • useReguide must be used inside ReguideProvider.
  • Pressing Escape closes the guide.
  • On the last step, Next is replaced with Close.

Theme Customization

Set defaults on the provider and override specific steps when needed.

<ReguideProvider
  steps={steps}
  theme={{
    backdrop: { color: '#020617', opacity: 0.7 },
    card: {
      background: '#ffffff',
      border: '1px solid #d6dce7',
      padding: 20,
      verticalOffset: 16,
      className: 'my-guide-card',
      style: { maxWidth: 420 },
    },
    title: { fontWeight: 700, color: '#0f172a' },
    body: { color: '#334155' },
    highlight: { borderRadius: 16, padding: 10 },
    stepCount: { show: true },
    buttons: {
      secondary: {
        background: '#f8fafc',
        border: '1px solid #cbd5e1',
        color: '#0f172a',
      },
      primary: {
        background: '#0f172a',
        border: '1px solid #0f172a',
        color: '#ffffff',
      },
    },
  }}
>
  {children}
</ReguideProvider>

Integration Checklist

Use this when the guide does not behave as expected.

| Issue | Verify | Resolution | | --- | --- | --- | | Error: useReguide must be used within a ReguideProvider | The component calling useReguide() is rendered under ReguideProvider. | Move that component inside ReguideProvider in the same React tree. | | Guide opens, but there is no spotlight cutout | The current step resolves a target via targetRef or step id registration, and the target element is mounted. | If no target resolves, reguide intentionally renders a centered card with a full backdrop. Add or fix target binding to get a cutout spotlight. | | goToStepById('...') does nothing | The id exists in steps and is unique. | Add the missing id, or rename duplicate ids. Unknown ids are ignored, and duplicate ids resolve to the last match. | | guide.start() is called but no card appears | steps contains at least one renderable step. | Provide at least one step. If there is no current step, the guide state can open without rendering a card. | | Guide renders, but default styles are missing | import 'reguide/style.css' is present in app startup code. | Add the stylesheet import once in your app entry path, for example main.tsx. |