Add an interactive checklist to WordPress walkthroughs: a plugin you can build today

· 4 min read

Most WordPress walkthroughs are static. Paragraphs, numbered lists, screenshots: they sit on the page the same way they did the moment someone loaded it. If you are writing a game guide, a setup tutorial, or a multi-step how-to, readers lose their place the moment they switch tabs, scroll back up, or come back the next day.

The obvious fix is a persistent checklist. Not a course platform, not a project management tool, not something that requires an account: just a list of steps where clicking marks them done and they stay marked on reload. That combination does not exist in standard WordPress, and the plugins that come closest bundle it inside booking systems, learning management platforms, or page builders that do much more than you need.

A shortcode closes the gap in a single PHP file. Wrap your steps in [checklist] tags and each becomes a checkbox. State is saved in the browser using localStorage, so nothing is written to your database, no account is required, and a reader who comes back tomorrow finds everything exactly as they left it.

What one build gives you

  • [checklist] shortcode that renders any list of steps as interactive checkboxes
  • Persistent state via localStorage: checked items stay checked on reload, no account required
  • Live progress counter that updates as steps are completed or unchecked
  • Scoped CSS with strikethrough and opacity change for completed steps

What it does not do

  • Cross-device sync: state is per-browser, not tied to a user account
  • Locked or sequential steps: all items are independently clickable from load
  • Completion tracking or analytics: no server-side reporting
  • Multiple independent checklists per page without a group attribute extension

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin called Walkthrough Checklist. Plugin Name: Walkthrough Checklist. Description: A shortcode that turns a list of steps into a persistent interactive checklist using browser localStorage. Single PHP file. No external dependencies. Register a shortcode 'checklist' using add_shortcode. The shortcode accepts inner content. If the content contains <li> tags, parse items from the text inside each <li>. If it contains no <li> tags, split on newlines and treat each non-empty trimmed line as one item. Strip HTML tags from each item to get clean text. For each item, generate a data-key by combining the post ID from get_the_ID(), a hyphen, and a sanitized version of the item text: strtolower, remove characters that are not alphanumeric or hyphens, truncate to 64 characters. This ensures keys are unique per post and do not collide across posts. Render each item as: <div class="wc-step"><label><input type="checkbox" class="wc-checkbox" data-key="{key}"> {item text}</label></div> Below all items, render: <p class="wc-progress"><span class="wc-done">0</span> of <span class="wc-total">{total}</span> steps completed.</p> Wrap everything in <div class="wc-checklist">. Use a module-level static boolean $enqueued = false. The first time the shortcode fires, set it to true and register wp_footer and wp_head hooks. In the wp_head hook, output a <style> block with: .wc-checklist { margin: 1.5em 0; } .wc-step { margin: 0.5em 0; } .wc-step label { cursor: pointer; display: flex; align-items: flex-start; gap: 0.5em; } .wc-step input[type=checkbox] { margin-top: 0.2em; flex-shrink: 0; } .wc-step.wc-checked label { text-decoration: line-through; opacity: 0.5; } .wc-progress { font-size: 0.9em; color: #666; margin-top: 1em; } In the wp_footer hook, output a <script> block that: on DOMContentLoaded, iterates every .wc-checkbox element and checks localStorage.getItem(el.dataset.key). If the value equals '1', sets el.checked = true and adds the class wc-checked to the parent .wc-step. After restoring all states, calls updateProgress() for each .wc-checklist. Adds a 'change' event listener on each .wc-checkbox that: calls localStorage.setItem(key, '1') and adds wc-checked to the parent .wc-step if checked; calls localStorage.removeItem(key) and removes wc-checked if unchecked; then calls updateProgress(checklist). The updateProgress function takes a .wc-checklist element, counts total .wc-checkbox children, counts checked ones, and updates the .wc-done span text.

Build this pluginAbout 55 credits · the free plan includes enough for one

Why numbered lists are not enough

A numbered list in WordPress does one thing: shows the steps. It has no memory of where the reader is. Open a guide on Monday, get halfway through, close the tab, and on Tuesday you are back at item one with no idea where you stopped.

Some people annotate with a notes app or highlight on screen. Most just scroll back through looking for where they were, which is friction at the exact moment they need none. For a five-step guide this is tolerable. For a forty-step game walkthrough or a software installation tutorial with branching paths, it is genuinely disruptive.

The checklist pattern solves it without requiring accounts or a database. The state lives in the browser, which means it is local, fast, and private. It also means it does not follow the reader to another device, but for most walkthrough use cases, that is the right tradeoff: simpler than a login wall, and free.

What the plugin does

The plugin registers a [checklist] shortcode. Wrap a list of steps inside the tags and each becomes a checkbox with a label. Checking an item saves its state to localStorage, keyed by the post ID and a sanitized version of the step text so the same step text on two different posts does not collide. Unchecking removes it.

A progress line below the list reads how many steps have been completed out of the total, and updates live as items are checked or unchecked. Completed steps are struck through and slightly faded, so the remaining work stands out at a glance.

The CSS and JavaScript are scoped to pages that contain the shortcode. Nothing loads on a post that does not use it. There are no external requests and no database writes.

  • [checklist] shortcode turns any list of steps into interactive checkboxes
  • State saved to localStorage: checked items persist across page reloads without an account
  • Live progress counter: N of M steps completed, updated on every check
  • Completed steps marked with strikethrough and reduced opacity
  • CSS and JavaScript only load on pages that use the shortcode

What the plugin does not do

Because state is stored in localStorage, it is per-browser. Someone who reads the guide on their phone and then continues on a laptop starts from the beginning on the second device. If cross-device progress sync matters, that requires user accounts and server-side storage, which this plugin does not include.

There is no concept of locked steps. All checkboxes are independently clickable from the start. If you want step 3 to be unavailable until steps 1 and 2 are checked, ask Steem to add that logic separately.

There is no completion notification, certificate, or analytics event. The checklist tells the reader where they are in their own browser; it does not report back to you. If you need completion tracking for analytics or compliance, this is not the right tool without extension.

Extending the plugin

The most common addition is a reset button at the bottom of each checklist that clears all localStorage entries for that post so a reader can run through the guide again from scratch. Ask Steem to add a [checklist reset_label='Start over'] attribute that appends a reset button.

For longer guides, a sticky progress bar at the top of the viewport showing overall completion percentage is a useful visual anchor. Ask Steem to add a fixed header strip that updates as items are checked.

If you have multiple checklists on a single page, for example a guide with one checklist per section, the shortcode can accept a group attribute to namespace the localStorage keys. Ask Steem to add [checklist group='section-one'] support so each section tracks its state independently.

Questions

Will the checklist reset if the reader clears their browser data?

Yes. The state is stored in the browser's localStorage. Clearing browser data, using a private browsing window, or switching to a different browser all start the checklist from scratch.

Does it work with the block editor or only the classic editor?

The shortcode works in both. In the block editor, add a Shortcode block and paste [checklist] with your steps inside it. The output is identical either way.

Can I use the shortcode more than once on the same page?

Yes. Each checklist is independent. The localStorage keys include the post ID and item text, so multiple checklists on the same post track their state separately as long as the item texts are distinct.

Do I need to format my items as an HTML list, or can I just type lines?

Either works. Type plain lines separated by line breaks inside the shortcode and each non-empty line becomes one item. Or use a proper <ul> or <ol> inside the shortcode and the plugin parses the <li> elements. Use whichever is easier in your editor.

What happens if two items have identical text?

They will share the same localStorage key, so checking one will show the other as checked on reload. Keep item text distinct, or ask Steem to add a numeric index to the key generation to handle duplicate labels.

The prompt

Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.

Build a WordPress plugin called Walkthrough Checklist. Plugin Name: Walkthrough Checklist. Description: A shortcode that turns a list of steps into a persistent interactive checklist using browser localStorage. Single PHP file. No external dependencies. Register a shortcode 'checklist' using add_shortcode. The shortcode accepts inner content. If the content contains <li> tags, parse items from the text inside each <li>. If it contains no <li> tags, split on newlines and treat each non-empty trimmed line as one item. Strip HTML tags from each item to get clean text. For each item, generate a data-key by combining the post ID from get_the_ID(), a hyphen, and a sanitized version of the item text: strtolower, remove characters that are not alphanumeric or hyphens, truncate to 64 characters. This ensures keys are unique per post and do not collide across posts. Render each item as: <div class="wc-step"><label><input type="checkbox" class="wc-checkbox" data-key="{key}"> {item text}</label></div> Below all items, render: <p class="wc-progress"><span class="wc-done">0</span> of <span class="wc-total">{total}</span> steps completed.</p> Wrap everything in <div class="wc-checklist">. Use a module-level static boolean $enqueued = false. The first time the shortcode fires, set it to true and register wp_footer and wp_head hooks. In the wp_head hook, output a <style> block with: .wc-checklist { margin: 1.5em 0; } .wc-step { margin: 0.5em 0; } .wc-step label { cursor: pointer; display: flex; align-items: flex-start; gap: 0.5em; } .wc-step input[type=checkbox] { margin-top: 0.2em; flex-shrink: 0; } .wc-step.wc-checked label { text-decoration: line-through; opacity: 0.5; } .wc-progress { font-size: 0.9em; color: #666; margin-top: 1em; } In the wp_footer hook, output a <script> block that: on DOMContentLoaded, iterates every .wc-checkbox element and checks localStorage.getItem(el.dataset.key). If the value equals '1', sets el.checked = true and adds the class wc-checked to the parent .wc-step. After restoring all states, calls updateProgress() for each .wc-checklist. Adds a 'change' event listener on each .wc-checkbox that: calls localStorage.setItem(key, '1') and adds wc-checked to the parent .wc-step if checked; calls localStorage.removeItem(key) and removes wc-checked if unchecked; then calls updateProgress(checklist). The updateProgress function takes a .wc-checklist element, counts total .wc-checkbox children, counts checked ones, and updates the .wc-done span text.

Build this pluginAbout 55 credits · the free plan includes enough for one

Read next

Other plugins you can build this way

Each loads into the composer, ready to edit.