---
title: "Epic Slider | Advanced Slider Element for YOOtheme Pro Websites"
description: "Create cinematic sliders in YOOtheme Pro with 34 transitions, video slides, animated text, Ken Burns effects and responsive navigation on every screen."
url: "https://www.yoozecase.com/?app_id=1&item_id=25"
date: "2026-09-28T03:38:29+00:00"
language: "en-GB"
---

#  Epic Slider

Always in transition

**Latest Version**
1.0.3
**Requires**
Joomla 5, 6, PHP 8.1 or newer
**Release date**
05 August 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/a0af2f20-fc91-43f6-b972-34dc8fec90d9)

 [ 🛒 Get it for WordPress👍 ](https://yoozecase.lemonsqueezy.com/checkout/buy/0b94eea1-11d6-43c8-8d97-28a8efdde8e0)

 [ Demo page ](https://www.yoozecase.com/epic-slider-demo)

Epic Slider is a premium slideshow element for YOOtheme Pro with 34 cinematic transitions, moving text, video slides and a real page-curl. Works in Joomla and WordPress, with no extra frameworks.

##  What's new in version 1.0.3

- **New:** The thumbnail strip follows the slider: on every slide change the active thumbnail is brought into view. The motion respects the reduced motion preference, and only the strip moves, never the page.
- **Fixed:** With more thumbnails than fit the slider width, the first thumbnails could not be reached or clicked. The strip was centred, so its contents overflowed on both sides while a scroll position can never go below zero. The strip now falls back to the start as soon as the row no longer fits, and stays centred while it does.
- **Fixed:** The horizontal scrollbar over the bottom of the slider is hidden. The strip still scrolls.

 ![Epic Slider 1](https://www.yoozecase.com/files/epic-slider_1.png)

 [](#)

 [](#)

---

##  Description

Epic Slider takes your hero section to the next level. Where most sliders stop at fading and sliding, Epic Slider gives you 34 transitions: tiles, spirals, blinds, sweeps, a 3D flip and a real page-curl where the page turns over with perspective, sheen and shadow. Every slide can have its own transition, so you decide the exact rhythm of your story.

You build everything right inside the YOOtheme Pro builder. Per slide you set a title, subtitle and button, with nine text animations (from a calm fade to letters that fly in and spin into place) and nine positions. Want moving imagery? Add an mp4, YouTube or Vimeo URL: it plays muted and looped, and by default fills the whole slider with no black bars. The slider loops endlessly.

For a modern hero, Epic Slider can slide underneath your logo and menu, with an adjustable header opacity and a readability gradient at the top. On top of that you get Ken Burns zoom, navigation with arrows, dots or thumbnails, swipe and mouse drag, a progress bar with play/pause, and a configurable alert box overlay (with a button or a newsletter field).

Key features:

- 34 transitions, with a per-slide effect override
- Real page-curl (Flipbook) with perspective and sheen
- Video slides (mp4, YouTube, Vimeo) that fill the whole frame, muted and looped
- 9 text animations, 9 positions, text panel or outline text
- Endless loop, Ken Burns, full-width and behind-header hero
- Navigation: arrows, dots, thumbnails, swipe and mouse drag
- Alert box overlay with a button or newsletter signup
- Lightweight, framework-free engine that respects reduced motion (accessible)
- For Joomla 5/6 and WordPress 6/7, with automatic updates

In short: a spectacular, fast and accessible slider that you set up in a few clicks and that looks sharp on every screen.

##  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 15/09/2026 ](#) Added- The thumbnail strip follows the slider. On every slide change the active thumbnail is brought into

 Fixed- With more thumbnails than fit the slider width, the first thumbnails could not be reached or
- The horizontal scrollbar over the bottom of the slider is gone. The strip still scrolls; only the

 [ v1.0.2 Breaking 06/09/2026 ](#) Changed- The pre-release check now flags a form field that redeclares a Joomla `FormField` method with a

 Fixed- On Joomla 5.4 the plugin settings page returned a server error (500). The licence key field

 [ v1.0.1 Breaking 11/08/2026 ](#) Fixed- The 1.0.0 package on the update server contained an older engine build in which sliders were

 [ v1.0.0 Breaking 11/08/2026 ](#) Added- \*\*Custom CSS per slider\*\*, on the Advanced tab. Rules are scoped to that one slider automatically,

- \*\*Navigation: Arrows + Thumbnails.\*\* Side arrows and the thumbnail strip can now be shown together.
- \*\*Consent for the newsletter form.\*\* The alert box signup now shows a required consent checkbox and
- \*\*Licence key.\*\* A Licence tab in the plugin settings with Activate and Deactivate, a status badge
- \*\*Build fingerprint.\*\* `BUILDINFO.txt` now carries a build id and a checksum over the package
- \*\*Preload hint for the first slide image.\*\* The first slider on a page now emits a
- \*\*Initialisation is spread out.\*\* When several sliders enter the viewport in the same

 Changed- Dead files are no longer shipped: `services/`, `src/Extension/` and the two `element.json` files

 Fixed- \*\*Custom CSS was missing entirely.\*\* The Advanced tab referenced YOOtheme's `${builder.css}`, which
- \*\*The consent checkbox and the email field shared a CSS rule.\*\* `.yc-slider\_\_alert-form input`
- \*\*The email field grew to 175 pixels tall below 480 pixels.\*\* In the stacked layout the main axis
- \*\*The alert bar could fall outside the slider.\*\* It is now bounded by the height of the slider and

 [ v0.12.0 06/08/2026 ](#) Added- The size readout now also reports the weight of the first slide and of all slides together. That

 Changed- Only the first image now holds up the start. The rest load in the background, one at a time, in
- Navigating to a slide whose image is not in yet waits for that one image and then continues, so a

 Fixed- The first image was drawn at the wrong scale. `\_build()` sizes the background before any image has
- Frame size changes other than a browser resize are now picked up as well, through a ResizeObserver

 [ v0.11.0 06/08/2026 ](#) Added- \*\*Custom height (px)\*\* as a Height option. The frame keeps the height you enter and the width runs

- \*\*Image width (px)\*\*: draw the image at a fixed pixel width instead of scaling it to the frame,
- \*\*Show image size\*\*: a design aid that puts a small readout on the slider with the dimensions and

 Changed- The Custom size field now serves both Custom ratio and Custom height. With Custom ratio it takes
- Worth knowing before you pick a fixed height: it does not crop less than a ratio does, it crops
- If you want no cropping at all, Custom ratio remains the answer: enter the dimensions of the image

 [ v0.10.0 06/08/2026 ](#) Added- Height can now follow the image instead of forcing the image into a fixed frame. Two new options

- Height and ratio can be set per YOOtheme breakpoint: S from 640 pixels, M from 960, L from 1200
- Three more presets in the Height field: Ratio 4:3, Ratio 3:1 and Ratio 1:1.
- New field \*\*Image fit\*\*: "Crop to fill" is the existing behaviour and stays the default, while
- New field \*\*Image position\*\* with nine positions, default centre. It decides which part of the

 Changed- Height and aspect ratio are now driven by CSS custom properties on the element (`--yc-ar`,
- The engine now computes the image rectangle through `\_fit()`, which handles both cropping and
- Existing sliders are unaffected. A saved Height keeps rendering the same frame, cropping stays the
- A typed ratio is validated strictly, since it ends up in a style attribute: two whole numbers with

 [ v0.9.9 05/08/2026 ](#) Changed- No change to the Joomla package. This release exists to keep the two platforms on the same

 [ v0.9.8 05/08/2026 ](#) Added- Admin notice when YOOtheme Pro is missing (YC.7). Without the YOOtheme Pro template the plugin

- Status indicator on the plugin settings page (YC.7). A new custom form field reports whether

 Changed- The Interval field now states its lower bound: "Time each slide stays visible. Minimum 2 seconds."
- License changed to GPL-2.0-or-later for all code in this package. Joomla and WordPress treat
- The engine, the slide templates and the styling are identical to 0.9.7. Brand, product names,
- Correction added on 2026-08-05: this entry originally claimed there was no functional change.

 Fixed- The product description counted 34 transition effects. The engine ships 37 (the flipbook
- The element icons in the builder picker now carry the required 24 x 24 size. They were 30 x 30

 Removed- `TERMS.txt` is no longer part of the package. The commercial terms (subscription, key usage,
- The redistribution ban, the modification ban and the AI ingestion ban are gone from the source

 [ v0.9.7 31/07/2026 ](#) Changed- All screen reader and button texts now go through the language layer. They were hardcoded and half

- Engine version to 0.9.0 (i18n layer and changed announcement behaviour).
- All five non-English language files had been stripped of every diacritical mark

 Fixed- Accessibility, from the live UI audit (`audit\_epic-slider\_v0.9.6\_ui-live.md`):
- Tap targets brought up to 44 x 44 px. Play/pause was 40 x 40, the close button of the alert box
- Contrast of the alert button from 3.51:1 to 17.8:1. The semi-transparent white fill on top of
- The focus ring now works on any background: a dark halo next to the white line, so it stays
- The aria-live announcement only fires on navigation the visitor starts themselves (arrow, dot,

## 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": "Epic Slider",
            "item": "https://www.yoozecase.com/"
        }
    ]
}
```
