• Resolved silvercarpet

    (@silvercarpet)


    I want to put a list of items on my website that visitors can click to change the appearance of the text. Think of it like a shopping list they can tick off once they’ve bought each item.

    Ideally, the text would be written in strikethrough after clicking to show they’ve done that thing, and it could be clicked on again to undo the strikethrough. For example, it would look like “Item A” to start with and “̶̶I̶t̶e̶m̶ ̶A̶” after being clicked. It could work instead with a check box, a toggle, or anything that let’s people tick off a list in their browser to save them having to use a pen and paper.

    The state of the items needs to be saved, so they don’t have to re-click things after refreshing the page or visiting on another day. I also want it to work without needing to sign up or log in.

    I started off looking for a Gutenberg block for this, but there isn’t one. Then I looked for a plug-in, but I can’t find any! I can’t believe such a simple function is missing from WordPress, and I read it’s been a requested feature since 2019.

    Can anyone help point me to any plug-in or solution?

    • This topic was modified 2 months, 4 weeks ago by silvercarpet.
    • This topic was modified 2 months, 4 weeks ago by silvercarpet.
    • This topic was modified 2 months, 4 weeks ago by silvercarpet.
Viewing 6 replies - 1 through 6 (of 6 total)
  • I am not sure you will find a native WP block that lets anonymous visitors “check off” items and remember them later. Maybe the simple way is to do it in the browser with localStorage (no login, saved per‑device/per‑browser). Add this in a Custom HTML block (or via WPCode/Code Snippets).

    1. Markup

    <ul id=”todo”> <li data-id=”a”>Item A</li> <li data-id=”b”>Item B</li> <li data-id=”c”>Item C</li> </ul>

    1. CSS

    <style> #todo li.done { text-decoration: line-through; opacity:.7; cursor:pointer; } #todo li { cursor:pointer; } </style>

    1. JS

    <script> document.addEventListener(‘DOMContentLoaded’, () => { const KEY = ‘todo-state-v1’; let saved = {}; try { saved = JSON.parse(localStorage.getItem(KEY)) || {}; } catch(e) {} document.querySelectorAll(‘#todo li’).forEach(li => { const id = li.dataset.id || li.textContent.trim(); const setState = (done) => { li.classList.toggle(‘done’, done); li.setAttribute(‘aria-checked’, done ? ‘true’ : ‘false’); if (done) saved[id] = true; else delete saved[id]; localStorage.setItem(KEY, JSON.stringify(saved)); }; // initial li.setAttribute(‘role’, ‘checkbox’); li.setAttribute(‘tabindex’, ‘0’); setState(Boolean(saved[id])); // interactions li.addEventListener(‘click’, () => setState(!li.classList.contains(‘done’))); li.addEventListener(‘keydown’, (e) => { if (e.key === ‘Enter’ || e.key === ‘ ‘) { e.preventDefault(); setState(!li.classList.contains(‘done’)); } }); }); }); </script>

    That will toggle strike‑through on click and remember it across refresh/days (on that device). If you prefer adding code safely, use WPCode: https://wordpress.org/plugins/insert-headers-and-footers/

    If you really need cross-device persistence without accounts, you’ll need a service (e.g., embed a Notion/Trello checklist) or build a tiny backend keyed by a shareable link – otherwise, I think that localStorage is the best no-login option.

    Unfortunately there is not any built in Gutenberg block or plugin that does what you are looking for. But I think you can achieve this quite easily writing custom JavaScript code with click function where you need to define where to put css text-decoration:line-through. I am providing you a sample output. You can modify as you need

    <ul id="shopping-list">
    <li>Option One</li>
    <li>Option Two</li>
    <li>Option Three</li>
    <li>Option Four</li>
    </ul>

    <script>
    document.addEventListener('DOMContentLoaded', () => {
    const list = document.getElementById('shopping-list');
    const savedState = JSON.parse(localStorage.getItem('checkedItems') || '{}');

    // Load saved states
    list.querySelectorAll('li').forEach((item, index) => {
    if (savedState[index]) {
    item.style.textDecoration = 'line-through';
    }

    item.addEventListener('click', () => {
    const isChecked = item.style.textDecoration === 'line-through';
    item.style.textDecoration = isChecked ? 'none' : 'line-through';
    savedState[index] = !isChecked;
    localStorage.setItem('checkedItems', JSON.stringify(savedState));
    });
    });
    });
    </script>

    Jasvir

    (@jasvirsaini)

    You can actually achieve this using a form-builder plugin like Gravity Forms or WPForms. Both let you create a list of choices that visitors can tick off and return later to see their progress.

    Gravity Forms:
    If you use Gravity Forms with the Advanced Save & Continue add-on (by Gravity Wiz), it offers auto-save and auto-load functionality. That means the user’s selections can be automatically saved and reloaded when they revisit — even without logging in.
    Check Here: https://gravitywiz.com/documentation/gravity-forms-advanced-save-and-continue/

    WPForms:
    WPForms has the Save & Resume add-on, which allows visitors to save their progress and continue later using a special link. However, in this case, users need to manually click the “Save & Resume” button.
    Check Here: https://wpforms.com/docs/how-to-install-and-use-the-save-and-resume-addon-with-wpforms/

    These tools are mainly built for creating forms, but they can work great for to-do lists too.
    You can let visitors check off items on a list, leave the page, and come back later to see their progress — all without needing to log in.

    I also found a great overview of current to-do/checklist plugin options in this blog: “Checklist and To-do list Plugins for WordPress in 2024” by Thomas Maier. He reviews what works and what falls short.
    Check here: https://thomas-maier.me/todo-list-checklist-plugins-wordpress/

    One more tip: you can achieve a nice “tick and strikethrough” effect via simple CSS once the state is saved (e.g., apply text-decoration: line-through; when an item is marked done). This means the visual “done” state can be styled cleanly even if the underlying plugin handles just the persistence.

    If you’d like to further match the form design to your website’s style, you can use these plugins:
    For Gravity Forms: https://wordpress.org/plugins/styles-and-layouts-for-gravity-forms/
    For WPForms: https://wordpress.org/plugins/styler-for-wpforms/

    Thread Starter silvercarpet

    (@silvercarpet)

    Thank you so much @mabfahad

    I put that code into a Custom HTML block and it worked perfectly first time! Foy anyone else who reads this, you just change the items listed at the top to whatever you want and it works exactly as I wanted.

    I needed more than one list on the page, so I used the same code but changed the name of the second list on the first line as shown below. Without this, neither list worked.

    <ul id="shopping-list2">

    And also changed line 10 to

    const list = document.getElementById('shopping-list2');

    I’m so pleased I have this answer, and if anyone from WordPress could make this into a Gutenberg block, I’m sure people would use it.

    Many thanks to @jasvirsaini too. I had thought of using forms, but couldn’t find one that saved progress. Gravity Forms looks interesting, so I’ll be taking a look into that as well.

    • This reply was modified 2 months, 4 weeks ago by silvercarpet.

    I am really glad it worked perfecly for you. And yes if you want independent list more than once on the same page each ne needs a unique id and JS must reference the corresponding id as you provided the example. However if you plan to have several of these lists on one page, you can make the script reusable by giving all lists shared class like shopping-list and looping through the

    <ul class="shopping-list">
    <li>Option One</li>
    <li>Option Two</li>
    <li>Option Three</li>
    <li>Option Four</li>
    </ul>

    <ul class="shopping-list">
    <li>Second List Option One</li>
    <li>Second List Option Two</li>
    <li>Second List Option Three</li>
    </ul>

    <script>
    document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('.shopping-list').forEach((list, listIndex) => {
    const storageKey =
    checkedItems_${listIndex};
    const savedState = JSON.parse(localStorage.getItem(storageKey) || '{}');

    list.querySelectorAll('li').forEach((item, itemIndex) => {
    // Restore saved state
    if (savedState[itemIndex]) {
    item.style.textDecoration = 'line-through';
    }

    // Handle clicks
    item.addEventListener('click', () => {
    const isChecked = item.style.textDecoration === 'line-through';
    item.style.textDecoration = isChecked ? 'none' : 'line-through';
    savedState[itemIndex] = !isChecked;
    localStorage.setItem(storageKey, JSON.stringify(savedState));
    });
    });
    });
    });
    </script>

    <style>
    .shopping-list li {
    cursor: pointer;
    transition: 0.2s;
    }
    .shopping-list li:hover {
    color: #0073aa;
    }
    </style>

    Thread Starter silvercarpet

    (@silvercarpet)

    I’ve had success with the to-di list provided by Md Abdullah Al Fahad, but I’m having trouble getting more than one list to work, even if they’re on different pages of my site.

    I’ve changed the name of the list in the first line of code so they say

    <ul id="shopping-list">

    and then

    <ul id="shopping-list2">

    and also changed the code on in the <script> section to

    const list = document.getElementById('shopping-list');

    and

    const list = document.getElementById('shopping-list2');

    but the lists all mix together. If I cross something out on shopping-list it also gets crossed out on shopping-list2.

    Is there a simple way I can just change something in the code to allow multiple lists on the same page?

    I used the code below by Md Abdullah Al Fahad but it doesn’t work at. Nothing gets crossed out on either list.

    <ul class="shopping-list">
    <li>Option One</li>
    <li>Option Two</li>
    <li>Option Three</li>
    <li>Option Four</li>
    </ul>

    <ul class="shopping-list">
    <li>Second List Option One</li>
    <li>Second List Option Two</li>
    <li>Second List Option Three</li>
    </ul>

    <script>
    document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('.shopping-list').forEach((list, listIndex) => {
    const storageKey =
    checkedItems_${listIndex};
    const savedState = JSON.parse(localStorage.getItem(storageKey) || '{}');

    list.querySelectorAll('li').forEach((item, itemIndex) => {
    // Restore saved state
    if (savedState[itemIndex]) {
    item.style.textDecoration = 'line-through';
    }

    // Handle clicks
    item.addEventListener('click', () => {
    const isChecked = item.style.textDecoration === 'line-through';
    item.style.textDecoration = isChecked ? 'none' : 'line-through';
    savedState[itemIndex] = !isChecked;
    localStorage.setItem(storageKey, JSON.stringify(savedState));
    });
    });
    });
    });
    </script>

    <style>
    .shopping-list li {
    cursor: pointer;
    transition: 0.2s;
    }
    .shopping-list li:hover {
    color: #0073aa;
    }
    </style>
Viewing 6 replies - 1 through 6 (of 6 total)

You must be logged in to reply to this topic.