---
title: "Quiz Whizz | Interactive Quiz Element for YOOtheme Pro Websites"
description: "Build engaging quizzes in YOOtheme Pro with multiple question types, personality tests, custom results, instant feedback, styling and accessibility built in."
url: "https://www.yoozecase.com/?item_id=27"
date: "2026-09-22T03:51:04+00:00"
language: "en-GB"
---

#  Quiz Whizz

You Puzzle Me

**Latest Version**
1.0
**Release date**
02 September 2026
**Made for**
Yootheme Pro, Joomla, Wordpress
**Price**
€25.00 (incl. 6 months of updates)

 [ 🛒 Get it for Joomla 👍 ](https://yoozecase.lemonsqueezy.com/checkout/buy/619af2cc-dbad-4f5f-a4b7-077383198aeb)

 [ 🛒 Get it for WordPress👍 ](https://yoozecase.lemonsqueezy.com/checkout/buy/6e64967b-1554-47b9-9347-fec95b9dad17)

 [ Demo page ](https://www.yoozecase.com/quiz-whizz-demo)

A powerful YOOtheme Pro quiz element for creating beautiful, interactive quizzes, self-tests and personality quizzes without code.

 ![Quiz Whizz](https://www.yoozecase.com/files/quiz-whizz.png)

 [](#)

 [](#)

---

##  Description

**Meet Quiz Whizz. The quiz element YOOtheme Pro was missing.**

Create engaging quizzes, self-tests, knowledge tests and personality quizzes directly in YOOtheme Pro. No separate quiz builder. No shortcodes. No wrestling with ugly forms or outdated interfaces.

Quiz Whizz gives you everything you need to build quizzes that actually feel like part of your website.

Choose between multiple choice, true or false, multiple answers, image answers, points-based self-tests and personality quizzes. Decide whether visitors get instant feedback or see their results at the end. Let questions move on automatically, shuffle questions and answers, set pass marks and create your own result profiles and score bands.

And because it is a proper YOOtheme Pro element, everything is configured right where you build your page. Pick a style preset, use your theme colours or choose your own accent, customise every button label and make the quiz fit seamlessly into your design.

Quiz Whizz also takes care of the details. Keyboard navigation, screen reader announcements, visible focus states, large touch targets and reduced-motion support are built in.

Need results? Store completed attempts in your own website database, view quiz statistics, collect contact details with consent and export your data to CSV. Or keep everything completely anonymous and let the quiz run entirely in the visitor's browser.

From a fun "How well do you know the web?" quiz to a lead-generating personality quiz or a serious-looking self-test, Quiz Whizz gives you the building blocks. You bring the questions.

**One element. Endless ways to quiz.**

Built for&nbsp, Joomla and WordPress with YOOtheme Pro.

##  Feature request

 ### What is the next feature for this plugin?

Submit your idea or vote on existing ones.

  #### Top voted

   Website (leave empty)

'; } html += ''; if (oc.title) { html += '##### ' + escapeHtml(oc.title) + '

'; } if (oc.text) { html += '' + escapeHtml(oc.text).replace(/\\n/g, '
') + '

'; } html += '

'; } // Auto winner(s) - top N from the already vote-sorted features if (mode === 'auto_winner' || mode === 'both') { var n = oc.winnerCount | 0; if (n '; for (var i = 0; i ' + (w.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(w.image_url)%20+%20')

' : '') + '' + (i + 1) + '' + '' + escapeHtml(w.title) + '' + '' + getIcon(this.cfg.voteIcon) + ' ' + (w.votes | 0) + '' + ''; } html += ''; } } if (html === '') { this.elOutcome.hidden = true; return; } this.elOutcome.innerHTML = html; this.elOutcome.hidden = false; }; /** * Roadmap view (v3.2.0): renders the items as status columns, in the * canonical status order from cfg.statuses. Each column is an 12. inside \* the existing .yc-fb-list , holding its own inner  of items - \* this keeps the delegated click/change handlers on elList working \* unchanged. Unknown statuses fall back into the "open" column so no item \* can silently disappear. Empty columns are skipped when \* cfg.roadmapHideEmpty is set. \*/ Board.prototype.renderRoadmap = function (items) { var stats = (this.cfg.statuses && this.cfg.statuses.length) ? this.cfg.statuses : \['open', 'planned', 'in\_progress', 'shipped', 'declined'\]; var labels = this.cfg.statusLabels || {}; var groups = {}; var i, st; for (i = 0; i ' + '##### ' + '' + escapeHtml(labels\[sv\] || sv) + '' + arr.length + '' + '

    ' + '' + itemsHtml + '
    ' + '
'; } return html; }; Board.prototype.renderItem = function (f, index) { var disabled = this.cfg.isClosed ? 'disabled' : ''; var voted = f.voted ? ' yc-voted' : ''; var descHtml = f.description ? '' + escapeHtml(f.description).replace(/\\n/g, '
') + '

' : ''; var thumbHtml = f.image_url ? '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

' : ''; // Status workflow (v3.0.0): badge for any non-default status. The // roadmap view (v3.2.0) skips the badge - the column already // communicates the status, a badge would be redundant noise. var st = (f.status || 'open'); var labels = this.cfg.statusLabels || {}; var statusBadge = (!this.isRoadmap && st && st !== 'open' && labels[st]) ? '' + escapeHtml(labels\[st\]) + '' : ''; // Design themes (v3.1.0): arena / neon / pixel render extra per-item // furniture - rank number, avatar (image or initials), a "leader" pill // on rank 1, and a "votes" label. Classic/colour themes are untouched. var t = this.cfg.theme || 'light'; var themed = (t === 'arena' || t === 'neon' || t === 'pixel'); var rank = (typeof index === 'number') ? (index + 1) : 0; var rankHtml = '', leaderHtml = '', avatarHtml = '', votesLabel = ''; if (themed) { rankHtml = '' + pad2(rank) + ''; if (rank === 1) { leaderHtml = '' + escapeHtml(this.cfg.leaderLabel || 'Leader') + ''; } if (f.image_url) { avatarHtml = '![](https://www.yoozecase.com/'%20+%20escapeHtml(f.image_url)%20+%20')

'; } else { avatarHtml = '' + escapeHtml(initialsOf(f.title)) + '

'; } votesLabel = '' + escapeHtml(this.cfg.votesLabel || 'votes') + ''; } // Moderation overlay: subtle corner buttons, only when canModerate. // Buttons appear on hover/focus (styled in CSS). Even in customizer // preview we render them when canModerate is true so the moderator // can see what visitors with moderation rights will see. var modHtml = ''; if (this.cfg.canModerate) { var parts = []; var hideTitle = escapeHtml(this.t('hideTitle', 'Hide this feature')); var deleteTitle = escapeHtml(this.t('deleteTitle', 'Delete this feature')); if (this.cfg.allowSoftHide) { parts.push( '' ); } if (this.cfg.allowHardDelete) { parts.push( '' ); } if (parts.length || (this.cfg.statuses && this.cfg.statuses.length)) { var sParts = parts.slice(); if (this.cfg.statuses && this.cfg.statuses.length) { var opts = ''; for (var si = 0; si ' + escapeHtml(sl) + ''; } var setStatus = escapeHtml(this.t('setStatus', 'Set status')); sParts.push( '' ); } modHtml = '' + sParts.join('') + '

'; } } var mediaHtml = themed ? avatarHtml : thumbHtml; return ( '32. ' + leaderHtml + rankHtml + '' + mediaHtml + '' + '##### ' + escapeHtml(f.title) + '

    ' + statusBadge + descHtml + '

    ' + modHtml + '
' ); }; /** * Detects whether a JSON response looks like a CSRF/token rejection. * Two cases: * 1. com_ajax-level rejection (Joomla's auto-token-check on POST): top * level message contains the JINVALID_TOKEN_NOTICE text and HTTP * status is typically 403. * 2. Plugin-level rejection from requireToken(): payload.error === "Invalid token". */ Board.prototype.isTokenError = function (status, raw, payload) { if (status === 403) return true; var hay = ''; try { hay = JSON.stringify(raw || {}) + '|' + JSON.stringify(payload || {}); } catch (e) {} return /invalid (security )?token/i.test(hay); }; /** * Fetch a fresh CSRF token from the server. Updates this.cfg.csrfToken so * the next call (and any future calls in this Board's lifetime) use the * correct one. Cache-bust with a timestamp so no upstream CDN caches the * response. */ Board.prototype.fetchFreshToken = function () { var self = this; var url = this.cfg.ajaxUrl + '&task=token&_=' + Date.now(); return fetch(url, { method: 'GET', credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Cache-Control': 'no-cache' } }) .then(function (r) { return r.json(); }) .then(function (data) { var p = self.unwrapAjax(data); if (p && p.ok === true && typeof p.token === 'string' && p.token.length >= 16) { self.cfg.csrfToken = p.token; return p.token; } throw new Error('No fresh token in response'); }); }; /** * Generic POST-with-token helper. Builds FormData from a plain object, * adds the current token, posts. On 403 / token-error response, fetches a * fresh token and retries exactly once. * * @param {string} task The task name (appended to URL and body) * @param {object} fields Key-value form fields (no token) * @return {Promise} Resolves to the unwrapped payload on success. \* Rejects with Error containing .reason on failure. \*/ Board.prototype.postWithToken = function (task, fields) { var self = this; function attempt(token) { var fd = new FormData(); fd.append('task', task); for (var k in fields) { if (Object.prototype.hasOwnProperty.call(fields, k)) { fd.append(k, fields\[k\]); } } fd.append(token, '1'); return fetch(self.cfg.ajaxUrl + '&task=' + encodeURIComponent(task), { method: 'POST', credentials: 'same-origin', body: fd, headers: { 'Accept': 'application/json' } }).then(function (r) { return r.json().then(function (data) { return { status: r.status, raw: data, payload: self.unwrapAjax(data) }; }); }); } return attempt(this.cfg.csrfToken).then(function (res) { // Success path if (res.payload && res.payload.ok === true) { return res.payload; } // Token rejection? Fetch fresh and retry once. if (self.isTokenError(res.status, res.raw, res.payload)) { return self.fetchFreshToken().then(function (fresh) { return attempt(fresh).then(function (res2) { if (res2.payload && res2.payload.ok === true) { return res2.payload; } var err = new Error( (res2.payload && res2.payload.error) || (res2.raw && res2.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res2.payload || null; throw err; }); }); } // Some other error - surface it var err = new Error( (res.payload && res.payload.error) || (res.raw && res.raw.message) || self.t('requestFailed', 'Request failed.') ); err.payload = res.payload || null; throw err; }); }; Board.prototype.handleSubmit = function () { var self = this; if (isInBuilder()) { this.showStatus(this.t('previewSubmit', 'Preview mode - submissions are disabled in the YOOtheme builder. Try this on the published page.'), 'info'); return; } if (this.cfg.isClosed) { this.showStatus(this.cfg.closedMessage || this.t('votingClosed', 'Voting is closed.'), 'info'); return; } var title = (this.elTitle && this.elTitle.value || '').trim(); var desc = (this.elDesc && this.elDesc.value || '').trim(); // Client-side validation: minimum title length. Server re-validates. var minLen = (this.cfg.minTitleLength | 0) || 2; if (title.length for hide/delete, remember parent + sibling for rollback. var li = btn.closest('li'); var parentEl = li ? li.parentNode : null; var nextEl = li ? li.nextSibling : null; if (li && (action === 'hide' || action === 'delete')) { li.remove(); } btn.disabled = true; var task = action === 'hide' ? 'moderate\_hide' : action === 'delete' ? 'moderate\_delete' : action === 'restore' ? 'moderate\_restore' : ''; if (!task) { btn.disabled = false; return; } this.postWithToken(task, { feature\_id: featureId }) .then(function (payload) { // Success - keep the UI as-is (already removed) and refresh // so vote counts, top box, and hidden-features panel reflect // the new state. self.refresh(); if (self.hiddenOpen) { self.hiddenLoaded = false; // force a refetch self.fetchHiddenFeatures(); } var msg = action === 'delete' ? self.t('deleted', 'Feature deleted.') : action === 'hide' ? self.t('hidden', 'Feature hidden.') : action === 'restore' ? self.t('restored', 'Feature restored.') : self.t('done', 'Done.'); self.showStatus(msg, 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { // Rollback: re-insert the row if we removed it. if (li && parentEl) { if (nextEl) { parentEl.insertBefore(li, nextEl); } else { parentEl.appendChild(li); } } btn.disabled = false; self.showStatus(err && err.message ? err.message : self.t('modFailed', 'Moderation action failed.'), 'error'); }); }; Board.prototype.handleStatusChange = function (featureId, status, sel) { var self = this; if (!featureId || !status) return; sel.disabled = true; this.postWithToken('moderate\_status', { feature\_id: featureId, status: status }) .then(function () { self.refresh(); var label = (self.cfg.statusLabels && self.cfg.statusLabels\[status\]) ? self.cfg.statusLabels\[status\] : status; self.showStatus(self.tf('statusSet', 'Status: %s', label), 'success'); setTimeout(function () { self.hideStatus(); }, 3000); }) .catch(function (err) { sel.disabled = false; self.showStatus(err && err.message ? err.message : self.t('statusFailed', 'Could not set status.'), 'error'); }); }; /\*\* \* Toggle the hidden-features panel open/closed. First open triggers \* an AJAX fetch; subsequent toggles just hide/show the cached list. \*/ Board.prototype.toggleHiddenPanel = function () { if (!this.elHiddenList || !this.elHiddenLabel) return; var arrow = this.elHiddenToggle && this.elHiddenToggle.querySelector('.yc-fb-hidden-arrow'); if (this.hiddenOpen) { this.hiddenOpen = false; this.elHiddenList.hidden = true; this.elHiddenLabel.textContent = this.t('showHidden', 'Show hidden features'); if (arrow) arrow.classList.remove('yc-open'); return; } this.hiddenOpen = true; this.elHiddenList.hidden = false; this.elHiddenLabel.textContent = this.t('hideHidden', 'Hide hidden features'); if (arrow) arrow.classList.add('yc-open'); if (!this.hiddenLoaded) { this.fetchHiddenFeatures(); } }; /\*\* \* Fetch the hidden-features list via the moderator-only endpoint. \* Re-callable to refresh the list after a restore. \*/ Board.prototype.fetchHiddenFeatures = function () { var self = this; if (!this.elHiddenList) return; this.elHiddenList.innerHTML = '2. ' + escapeHtml(this.t('loading', 'Loading...')) + '
'; var url = this.cfg.ajaxUrl + '&task=moderate\_list\_hidden&board\_id=' + encodeURIComponent(this.cfg.boardId); fetch(url, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } }) .then(function (r) { return r.json(); }) .then(function (data) { var payload = self.unwrapAjax(data); if (!payload || payload.ok !== true) { self.elHiddenList.innerHTML = '4. ' + escapeHtml((payload && payload.error) ? payload.error : self.t('hiddenLoadFailed', 'Failed to load hidden features.')) + '
'; return; } self.hiddenLoaded = true; var features = payload.features || \[\]; if (features.length === 0) { self.elHiddenList.innerHTML = '6. ' + escapeHtml(self.t('noHidden', 'No hidden features.')) + '
'; return; } var html = ''; for (var i = 0; i ' + escapeHtml(f.description).replace(/\\n/g, '
') + '' : ''; var restoreTitle = escapeHtml(self.t('restoreTitle', 'Restore this feature')); html += '9. ' + '' + '##### ' + escapeHtml(f.title) + '

    ' + descHtml + '' + escapeHtml(self.tf('votesMeta', '%s votes', f.votes | 0)) + '' + '

    ' + '' + '
'; } self.elHiddenList.innerHTML = html; }) .catch(function () { self.elHiddenList.innerHTML = '11. ' + escapeHtml(self.t('hiddenNetwork', 'Network error while loading hidden features.')) + '
'; }); }; function initAll(scope) { var roots = (scope || document).querySelectorAll('.yc-fb-root'); for (var i = 0; i ---

##  Changelog

---

 [ v1.0.3 Breaking 07/09/2026 ](#) Changed- Question and answer numbers in the stored answers are the builder order,

 Fixed- The contact form now really waits for the attempt to be stored. submit()
- With Shuffle answers on, the letters A to F are put back in reading order
- A knowledge question without a correct answer (a poll between the test
- A number key pressed on the intro card no longer answers the first question
- A custom accent written as hsl(), a colour name or an eight digit hex now

 [ v1.0.2 Breaking 06/09/2026 ](#) Changed- The Store Results description in the quiz element no longer claims that no

- NOTICE.txt says which of the two storage switches is on after install (the

 Fixed- Saving the plugin options no longer undoes a licence activation. The
- The marker of the last daily summary is now declared in the options form.
- The daily summary window now starts at the moment the previous summary
- The "last checked" moment in the licence panel and the "when" line in the

 [ v1.0.1 Breaking 04/09/2026 ](#) Changed- `NOTICE.txt` said "The plugin makes no external calls". That stopped being

- `TERMS.txt` is no longer part of the package (YC.8). The terms live at
- `PLG\_SYSTEM\_YC\_QUIZ\_WHIZZ\_LIC\_INTRO` already names the Joomla updater in all

 Fixed- The edge of an answer button did not reach 3:1 (WCAG 1.4.11) on a theme
- The Try again button on the result card sat straight against the frame. The
- The licence panel had no edge, fill or radius. The `--yc-qw-\*` custom

 [ v1.0.0 Breaking 02/09/2026 ](#) Added- Licence panel in the plugin settings. Paste a key, activate it, and updates

- The activation slot is released when the plugin is uninstalled, so a key
- Update server and changelog feed, both linked from the manifest, so a new
- TERMS.txt, which says plainly what a subscription buys and what it does not,

 Changed- A licence question never breaks a site. The verdict is cached for a day, and
- Only an HTTP 200 with a parseable body counts as a verdict about a key.
- The licence client is GPL like the rest of the package. Selling it and

 Security- The administrator tasks on the shared AJAX endpoint are refused before

 [ v0.9.2 01/09/2026 ](#) Added- Minified stylesheets and scripts alongside the source. Joomla already asked

 Fixed- The edge that marks a chosen, right or wrong answer now reads at least as

 [ v0.9.1 01/09/2026 ](#) Fixed- A quiz with a custom accent colour got none of the readable colours from

- The colours that carry text are fitted with a little headroom above the
- The answer buttons in the Filled preset had the same faint edge the other

 [ v0.9.0 01/09/2026 ](#) Added- Question Element: the question can be a paragraph, as it always was, or a

 Changed- Answers tell a screen reader what kind of choice they are: one out of several
- The spoken announcement says which question you are on, and no longer says
- The narrow-screen breakpoint moved from 420 to 480 pixels. At 420 a 412
- All direction-dependent styling uses logical properties, so the quiz mirrors
- The contrast maths lives in four pure functions in the engine and is
- The accent keeps its own colour for borders and the focus ring, which need
- The edge colour is behind an @supports check, so a browser without color-mix

 Fixed- A chosen, right or wrong answer now colours its card in every style preset.
- The focus no longer disappears three times. Pressing start, finishing, and
- Text on the accent colour is readable whatever the accent is. White was
- The accent as text has its own colour. The question counter above each
- Passed and failed read at full contrast. The green measured 2.9:1 on a light
- The answers you did not pick stay readable after a question locks. They
- Answer cards have a visible edge. The card fill sat at 1.1:1 against the page

 [ v0.8.0 01/09/2026 ](#) Added- Frame, a setting of its own next to the style preset. Follow the style

- A quiz built before this setting existed keeps exactly the look it had:

 [ v0.7.2 01/09/2026 ](#) Fixed- The intro image never appeared. YOOtheme wraps the image in a picture

- The intro image loads eagerly. It sits on the card the visitor meets
- Above the text the image is capped at 260 pixels wide and centred,

 [ v0.7.1 01/09/2026 ](#) Fixed- The intro icon rendered as a literal icon tag instead of an icon. The

## Schema

```json
{
    "@context": "https://schema.org",
    "@type": "Organization",
    "@id": "https://www.yoozecase.com/#organization",
    "name": "YOOzeCase",
    "alternateName": "A YOOzeCase Company",
    "description": "Premium plugins, extensions and YOOtheme Pro elements for WordPress and Joomla. A YOOzeCase Company: polished tools built for usability and performance.",
    "email": "info@yoozecase.com",
    "url": "https://www.yoozecase.com",
    "foundingDate": "2026-01-01",
    "logo": "https://www.yoozecase.com/images/yoozecase/Yoozecase logo.png",
    "image": "https://www.yoozecase.com/images/yoozecase/Yoozecase logo.png",
    "address": {
        "@type": "PostalAddress",
        "addressCountry": "NL",
        "addressLocality": "Rotterdam"
    },
    "sameAs": [
        "https://www.facebook.com/yoozecase",
        "https://x.com/yoozecase",
        "https://www.instagram.com/yoozecase",
        "https://www.youtube.com/@yoozecase",
        "https://bsky.app/profile/yoozecase.bsky.social",
        "https://www.threads.com/@yoozecase",
        "https://mastodon.social/@yoozecase",
        "https://www.reddit.com/user/yoozecase"
    ]
}
```

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "name": "Home",
            "item": "https://www.yoozecase.com"
        },
        {
            "@type": "ListItem",
            "position": 2,
            "name": "Extensions",
            "item": "https://www.yoozecase.com/extensions"
        },
        {
            "@type": "ListItem",
            "position": 3,
            "name": "Quiz Whizz",
            "item": "https://www.yoozecase.com/"
        }
    ]
}
```
