@stepr/checklist v0.1.0

Onboarding checklists that stay out of your way

A small, dependency-free checklist widget for any web app, with or without a framework. It renders native buttons and announces progress politely, and it only completes a step when your app says the work is done.

npm install @stepr/checklist

Live demo

Acme is a made-up project tool. Open a step from the checklist or use the app's toolbar. Each step is completed by the app event that finishes it, such as saving a form or finishing a tour, not by the click that opens it.

Acme Projects Setup 0/0

Welcome to Acme

Nothing here yet. Open a step in the checklist to set up your workspace.

Try the API

Widget theme (CSS custom properties)

Events

Callbacks and return values appear here.

    Examples

    Copy any example as is. The JavaScript also works as TypeScript because the package ships its own types. Tests run these exact snippets against the built package and check that they match the README.

    Quick start without a bundler

    The package is ESM only. With a bundler, just import it. Without one, map the package name in an import map and serve the page over HTTP. ES modules don’t load from file://.

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>Checklist</title>
        <script type="importmap">
          {
            "imports": {
              "@stepr/checklist": "/node_modules/@stepr/checklist/dist/index.js"
            }
          }
        </script>
      </head>
      <body>
        <div id="onboarding"></div>
        <script type="module">
          import { createChecklist } from '@stepr/checklist';
    
          createChecklist({
            container: '#onboarding',
            title: 'Get started',
            steps: [
              {
                id: 'profile',
                label: 'Complete your profile',
                onClick: (step, checklist) => checklist.complete(step.id),
              },
              { id: 'docs', label: 'Read the docs' },
            ],
          });
        </script>
      </body>
    </html>

    Complete steps from your app

    Activating a step only calls its onClick. Mark a step done when the work actually happens, from any code that holds the checklist. Steps without onClick are rendered as text.

    import { createChecklist } from '@stepr/checklist';
    
    const checklist = createChecklist({
      container: '#onboarding',
      title: 'Get started',
      steps: [
        {
          id: 'profile',
          label: 'Complete your profile',
          onClick: () => openProfileForm(), // opening the form does not complete it
        },
        { id: 'verify', label: 'Verify your email address' }, // text only
      ],
      onChange: ({ progress }) => {
        if (progress.completed === progress.total) showAllDoneMessage();
      },
    });
    
    // Complete steps when the work is actually done, from anywhere in your app:
    onProfileSaved(() => checklist.complete('profile'));
    onEmailVerified(() => checklist.complete('verify'));

    Persistence, reset and cleanup

    Persistence is opt-in. Completed step ids are stored in localStorage under your key. If storage is unavailable or full, the checklist keeps working in memory.

    import { createChecklist } from '@stepr/checklist';
    
    const checklist = createChecklist({
      container: '#onboarding',
      title: 'Get started',
      steps: [
        { id: 'profile', label: 'Complete your profile' },
        { id: 'invite', label: 'Invite a teammate' },
      ],
      storageKey: 'my-app:onboarding', // restored on creation, saved on change
    });
    
    checklist.complete('invite'); // true, and saved
    checklist.complete('invite'); // false: already complete, nothing saved
    checklist.getProgress(); // { completed: 1, total: 2 }
    
    checklist.reset(); // marks every step incomplete, and saves that
    checklist.destroy(); // removes the widget; saved progress is kept
    localStorage.removeItem('my-app:onboarding'); // forget saved progress

    Start a Stepr tour from a step

    The checklist doesn’t know about tours. Your app connects the two libraries with callbacks: the step’s onClick starts the tour, and the tour’s onComplete completes the step. Closing the tour early leaves the step open. Stepr is a separate package that you install yourself.

    import { createChecklist } from '@stepr/checklist';
    import { createTour } from 'stepr';
    
    const tour = createTour({
      steps: [
        { target: '#new-project', title: 'Projects', description: 'Start here.' },
        { target: '#invite', title: 'Invite your team' },
      ],
      onComplete: () => checklist.complete('tour'), // finished: tick it off
      onExit: () => {}, // closed early: the step stays incomplete
    });
    
    const checklist = createChecklist({
      container: '#onboarding',
      title: 'Get started',
      steps: [
        {
          id: 'tour',
          label: 'Take the product tour',
          onClick: () => {
            if (!tour.start()) console.warn('No tour target is visible');
          },
        },
      ],
    });

    Theme with CSS custom properties

    Set the properties on the widget or on any ancestor, such as the container. This is the demo’s “Violet” theme. If you change colors, check the contrast yourself.

    /* <div id="onboarding" class="theme-violet"></div> */
    .theme-violet {
      --stepr-checklist-accent: #6d28d9;
      --stepr-checklist-focus: #6d28d9;
      --stepr-checklist-background: #faf5ff;
      --stepr-checklist-hover: #f3e8ff;
      --stepr-checklist-border: #8b5cf6;
      --stepr-checklist-radius: 1rem;
    }

    Reference

    A summary. The README has the full API and behavior details.

    Exports

    createChecklist(options)
    Renders the widget and returns the controller methods plus element and destroy().
    createChecklistController(options)
    The same state without any DOM, for building your own UI.
    checklistCss
    The default stylesheet as a string, for use with injectStyles: false.

    Methods

    complete(id) / uncomplete(id)
    Return true if the state changed. Unknown ids throw.
    reset()
    Marks every step incomplete.
    click(id)
    Calls the step’s onClick; never completes it.
    isCompleted(id), getProgress(), getState()
    Read state as fresh snapshots.
    subscribe(fn)
    Runs after every change. Returns an unsubscribe function.

    Accessibility

    • Steps with onClick are native buttons, so mouse, touch, Enter and Space all work.
    • Each step’s accessible name includes its state, for example “Complete your profile, completed”.
    • The progress text is a polite live region. Focus stays put when state changes.
    • Visible focus outline, 44px targets, and no transitions when reduced motion is requested.

    Not yet tested with a screen reader. See the manual test in docs/manual-testing.md.

    CSS custom properties

    Read from the checklistCss export of the build this page loaded.

    Property Default