---
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-11-06T20:24:52+00:00"
language: "en-GB"
---

#  Quiz Whizz

You Puzzle Me

**Latest Version**
1.3.3
**Requires**
YOOtheme Pro 5+, Joomla 5+, WordPress 6.5+, PHP 8.1 or newer
**Release date**
02 November 2026
**Made for**
YOOtheme Pro, Joomla, WordPress
**License**
GPL-2.0-or-later
**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.

##  What's new in version 1.3.3

- **Fixed:** Joomla: installing an update emptied a Download Key entered by hand under System, Update Sites while no licence was active. The key could then not be taken over, and Joomla reported "The Download Key is missing". The key now stays, and the plugin activates it on the next administrator page.

 ![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.3.3 02/11/2026 ](#) Fixed- Joomla: installing an update emptied a Download Key entered by hand

 [ v1.3.2 31/10/2026 ](#) Added- Joomla: when the key under System, Update Sites is not accepted, the

 Changed- Joomla: a licence key entered as Download Key under System, Update Sites

 [ v1.3.1 30/10/2026 ](#) Changed- WordPress: tested up to WordPress 7.1. The Updates screen now shows the

 Fixed- Deactivating the key while the licence server was busy removed the key
- Entering the same key again, after a renewal or with a second click,
- Entering a different key left the activation of the old key taken. The
- After a server clock that ran ahead had been corrected, one moment
- Joomla: when the licence state could not be saved, every administrator
- Joomla: a Download Key typed in by hand under System, Update Sites is no
- A field linked to Dynamic Content with several values, or a damaged
- The Advanced tab of the quiz element (Margin, Class, ID, Attributes,

 [ v1.3.0 11/10/2026 ](#) Changed- Updates need an activated key. Joomla: the update site carries the key as

- WordPress: the licence is checked under the product name yc\_quiz\_whizz
- The licence check asks again once a day for every stored status, so a
- The deciding question and the stored answer numbers follow the order in
- The builder preview says so when the deciding question has no text or
- WordPress: a new version shows up within the hour, and at once after
- Licence files: LICENSE.txt carries the full GPL text, the trademark note
- Joomla: the licence key is no longer printed in the page source of the

 Fixed- A contact left just after the daily summary had run, for an attempt
- When a finished quiz could not be stored (too many attempts in a short
- Joomla: two requests at the first page view of a day could both send the
- Joomla: the link in a notification mail followed the address of the
- Joomla: quiz and outcome titles are cleaned the same way as on
- WordPress: one visitor could use up the site-wide limit on stored

 Security- A site-wide limit of 500 stored attempts per hour on both platforms

 [ v1.2.0 29/09/2026 ](#) Added- Image and button per outcome and per score band. Add a picture under the

- Result Element: make the result a heading (H2, H3 or H4), so a screen
- A check in the builder preview. It lists mistakes the builder cannot show:
- A notice in the administrator when the licence key has expired or was

 Changed- The result heading is larger (1.25 times the text size), so it reads as
- The builder lists questions by their text instead of "Question 1",
- "Points A" is now "Points or Weight A", because in a personality quiz the
- Every field in the element has help text, and the short ones say more.
- The plugin description names all three quiz modes.
- The element icon uses the Quiz Whizz colour and the size YOOtheme uses for
- The daily summary holds up to 500 contacts (was 200) and says so when
- The profile bars use the readable variant of the accent.

 Fixed- Two texts in the administrator did not reach the contrast WCAG asks for:
- The dashboard buttons are 44 pixels high, like the other controls.
- Points in the notification mails are written as 12 or 12.5, not 12.00.
- The dashboard shows the newest title of a quiz, not the alphabetically
- French and Italian: a number of texts missed their apostrophe ("n a pas"
- Joomla: the notification mails are written in the default administrator
- Joomla: the daily summary marker is written onto the stored plugin
- Joomla: the token request is never cached, so a page cache cannot hand

 [ v1.1.0 29/09/2026 ](#) Added- On A Tie, per quiz. Let One Question Decide looks at a question you pick:

- Tie Text, for your own wording of the line under a tie.
- Profile, per quiz: a bar per outcome with its share of the answers, highest
- Four new strings, in all six languages.

 Changed- When two or more outcomes end with the same total, the result card shows
- A stored tie gets the numbers of all tied outcomes as its key, for example
- The statistics count a tie towards each tied outcome and say how many

 Fixed- An answer linked to an outcome the quiz does not have (outcome 5 in a quiz

 [ v1.0.3 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 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 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 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

## Schema

```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/"
        }
    ]
}
```
