Video Testimonials Carousel for Elementor
Elementor widget: a carousel of testimonial cards where video is optional per card. Nothing video-related loads until a card is clicked. Auto-detects YouTube, Vimeo and self-hosted video, with VideoObject schema.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/mehdibouchami/video-testimonials-carousel/archive/refs/heads/main.zipAn Elementor widget: a carousel of portrait testimonial cards where each card may open a video in place. Cards with no video URL stay plain images — no play icon, no click behaviour, no JavaScript attached to them.
No iframe or <video> element exists on the page until a visitor clicks a card, so YouTube, Vimeo and your
own media files cost nothing on load.
Requirements
- WordPress 5.9+
- PHP 7.4+
- Elementor 3.5+ (the free plugin — Elementor Pro is not needed)
Install
- Copy the
video-testimonials-carouselfolder intowp-content/plugins/, or zip that folder and upload it under Plugins → Add New → Upload Plugin. - Activate Video Testimonials Carousel.
- Edit a page with Elementor and search the widget panel for Video Testimonials Carousel.
Adding cards
Each row under Content → Testimonials → Cards has three fields:
| Field | Purpose |
|---|---|
| Label | Editor only. Names the row in the panel, and becomes the image alt text and the play button's screen-reader label. Nothing is printed on the card. |
| Card Image | What visitors see. Cropped to the card's aspect ratio. |
| Video URL | Optional. Leave empty for a plain image card. |
The provider is detected from the URL — there is nothing to select:
youtube.com/watch?v=…,youtu.be/…,/shorts/…,/embed/…,/live/…vimeo.com/123456789,player.vimeo.com/video/…, and unlisted links carrying a privacy hash (vimeo.com/123456789/abc123or?h=abc123)- any direct media file:
.mp4,.m4v,.webm,.ogv,.ogg,.mov— including files from your WordPress Media Library
A URL that matches none of these is treated as "no video": the card renders as a plain image rather than breaking.
Behaviour while a video plays
- Only one video plays at a time, anywhere on the page. Opening a second card tears the first one down.
- A video stops when its card leaves the screen (Video → When Scrolled Out Of View). This matters most
on phones showing one card at a time, where swiping on would otherwise leave a video talking out of sight.
It is measured from how much of the card is actually visible rather than from the slide index, so it behaves
the same whether you show one card or four, and it catches scrolling past the carousel as well as swiping
within it.
Pausekeeps the position and resumes when the play button is clicked;Keep playingis the old behaviour. - Arrows and dots hide themselves where they are pointless. At any breakpoint where every card already fits — five cards shown five per row, say — the controls disappear, and they return at breakpoints where the cards do not fit, such as one per row on a phone. This is decided per breakpoint from the card count against slides per view, not from Swiper's own state, which never reports "nothing to scroll" while looping is on because looping adds duplicate slides.
- Carousel autoplay pauses, and resumes when the video ends or is closed.
- The card returns to its image when the video ends (Video → Return To Image When Video Ends).
- A close button sits over the player (Video → Close Button While Playing).
Settings worth knowing
- Video → Show Play Button — turn this off if your card images already have a play button drawn on them. The cards stay clickable either way.
- Video → Video Fit —
Containletterboxes a 16:9 video inside a portrait card;Covercrops it to fill. - Video → Privacy Mode — embeds via
youtube-nocookie.comand sends Vimeodnt=1, so no tracking cookies are set before playback. - Video → Crop Provider Chrome — the only way to remove YouTube's title and channel bar. See the note below.
- Video → Preload — applies only after a click; nothing is fetched before that.
- Video → On iPhone And iPad — iOS grants sound only to a tap on the player itself, and a tap on the card is not a gesture inside a cross-origin iframe. So a YouTube or Vimeo embed that starts by itself can only ever start muted, whatever Start Muted says. Set this to Wait for one tap and the embed loads showing the provider's play button instead; that tap plays with sound. Costs iOS visitors one extra tap, affects embeds only, and leaves every other device untouched. Self-hosted video already plays with sound everywhere.
- Style → Card → Aspect Ratio — 9:16 by default, with 4:5, 1:1 and 16:9 available per device.
- Style → Arrows → Position — Over the cards (default) or Beside the cards. Beside reserves a gutter the width of one arrow plus the offset on each side, so the arrows never cover a card and never spill into whatever sits next to the carousel. The carousel narrows by both gutters, which is expensive at phone widths — the control is per device, so you can have them beside on desktop and over the cards on mobile.
SEO
Click-to-play is good for speed and privacy but bad for video indexing, and Google says so directly: "Don't rely on user actions (such as swiping, clicking, or typing) to load the video." (Video SEO best practices)
The widget resolves that by declaring the videos independently of the DOM. With SEO → VideoObject Schema on, every card that has a video emits JSON-LD:
| Property | Where it comes from | |
|---|---|---|
name |
Video Title, falling back to the card Label | required |
thumbnailUrl |
the card image at full size | required |
uploadDate |
Upload Date, falling back to the page's publish date | required |
embedUrl / contentUrl |
derived from the video URL | required |
description |
Video Description | recommended |
duration |
Duration, converted from 1:23 to PT1M23S |
recommended |
A card is left out of the markup if it has no video, no title or no image, because Google treats items missing a required property as ineligible rather than partially valid. Switch the whole thing off if Yoast, Rank Math or similar already emits video schema for these pages — two VideoObject blocks for one video is worse than none.
Fill in a real upload date and duration where you can. The page-date fallback keeps the markup valid, but it is not true, and accuracy is what earns rich results.
Other search-facing details handled for you:
- The first card image loads eagerly with
fetchpriority="high"(SEO → Preload First Images), so the carousel does not become a lazy-loaded Largest Contentful Paint element. - Images carry
widthandheight, so cards reserve their space and do not shift. - The carousel is exposed as a labelled
regionwitharia-roledescription="carousel".
Support
Found a bug or need a hand? Open an issue on GitHub, or email 75722625+mehdibouchami@users.noreply.github.com.
If the plugin saved you some time, you can buy me a coffee on Ba9chich.
A note on YouTube branding
Vimeo lets you hide its chrome completely, which is why a Vimeo card looks clean. YouTube does not, and no embed parameter changes that:
modestbranding=1has been ignored since August 2023. The plugin still sends it; it does nothing.showinfo=0, which used to hide the title, was removed in 2018.controls=0hides only the control buttons. The title, channel name, avatar and the Shorts watermark stay on screen. (Verified against a Shorts embed, both states side by side.)
The chrome is hidden while the video plays uninterrupted and reappears on hover or pause — which is when most people notice it.
The one thing that works is cropping it: Video → Crop Provider Chrome. This scales the player taller
than the card so the header and the watermark fall outside the visible area. About 130% clears both. The
trade-off is a zoomed-in video that loses a little off the top and bottom, so it is set to 100% (off) by
default — Vimeo and self-hosted video need no cropping.
If a completely unbranded, uncropped player matters more than hosting on YouTube, serve those testimonials as self-hosted MP4s from the Media Library: the HTML5 player has no branding of any kind.
A note on "return to image when video ends"
For self-hosted video this is exact (the browser's own ended event). For YouTube and Vimeo it uses each
provider's postMessage channel rather than loading their JavaScript SDKs, which keeps the "nothing loads
until clicked" promise. That channel is reliable in practice but not guaranteed — if a provider ever stops
reporting the end of playback, the card simply stays on the player, and the close button still works.