civil-and-structural-engineering
Using Javascript to Build a Custom Content Slider for Testimonials
Table of Contents
Building a custom content slider for testimonials using JavaScript gives you full control over design, behavior, and performance. Unlike pre-built plugins, a hand‑coded slider integrates seamlessly with any CMS, including Directus, and can be tailored to match your brand’s exact specifications. This article walks through creating a production‑ready testimonial slider from scratch, covering the HTML skeleton, CSS transitions, JavaScript logic, navigation controls, responsive considerations, accessibility, and – crucially – how to fetch live testimonial data from Directus via its REST API.
Planning the HTML Structure
A testimonial slider consists of a container and individual slides. Each slide holds the quote, the client’s name, and optional metadata such as a title or company logo.
<div id="testimonial-slider" class="slider">
<div class="slide active">
<blockquote>
<p>“Great service that delivered exactly what we needed.”</p>
<footer>— Jane Doe, CEO of Example Inc.</footer>
</blockquote>
</div>
<div class="slide">
<blockquote>
<p>“Their team was responsive and professional from start to finish.”</p>
<footer>— John Smith, Founder of Acme Corp.</footer>
</blockquote>
</div>
<div class="slide">
<blockquote>
<p>“We saw a measurable improvement in user engagement after the redesign.”</p>
<footer>— Emily Chen, CTO of WebTech</footer>
</blockquote>
</div>
</div>
Each slide wraps a <blockquote> element for semantic correctness and better accessibility. The active class determines which slide is currently visible. This structure can be hard‑coded or, better, generated dynamically from data fetched via JavaScript.
Styling the Slider with CSS
Clean, modern CSS ensures the slider looks polished and performs well. Use overflow: hidden on the container and control slide visibility with opacity and transitions.
.slider {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto;
overflow: hidden;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.slide {
display: none;
padding: 2rem;
background: #f9f9f9;
transition: opacity 0.5s ease-in-out;
}
.slide.active {
display: block;
}
blockquote {
margin: 0;
padding: 0;
font-style: italic;
line-height: 1.6;
}
blockquote footer {
margin-top: 0.75rem;
font-style: normal;
font-weight: 600;
color: #333;
}
.slider-nav {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 1rem;
}
.slider-nav button {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid #007acc;
background: transparent;
cursor: pointer;
transition: background 0.3s;
}
.slider-nav button.active-dot {
background: #007acc;
}
Using display: none with a class toggle is simple but lacks smooth transitions between slides. For a production slider, consider using CSS transforms or absolute positioning to achieve crossfade or slide animations. The above approach works well for a minimal setup.
Core JavaScript Functionality
The JavaScript engine manages slide cycling, dot navigation, and user interaction. Start by selecting the slides and initializing an index.
const slides = document.querySelectorAll('.slide');
const dots = document.querySelectorAll('.slider-nav button');
let currentIndex = 0;
let intervalId;
function showSlide(index) {
slides.forEach((slide, i) => {
slide.classList.toggle('active', i === index);
});
dots.forEach((dot, i) => {
dot.classList.toggle('active-dot', i === index);
});
currentIndex = index;
}
function nextSlide() {
const next = (currentIndex + 1) % slides.length;
showSlide(next);
}
function startAutoPlay(interval = 5000) {
intervalId = setInterval(nextSlide, interval);
}
function stopAutoPlay() {
clearInterval(intervalId);
}
// Dot click handler
dots.forEach((dot, index) => {
dot.addEventListener('click', () => {
stopAutoPlay();
showSlide(index);
startAutoPlay(4000); // restart with shorter delay after manual interaction
});
});
// Initialize
showSlide(0);
startAutoPlay(5000);
This code provides automatic rotation and interactive dot navigation. For enhanced usability, pause autoplay when the user hovers over the slider or interacts with the dots.
Adding Previous / Next Buttons
Many users expect arrow controls to manually browse testimonials. Add two buttons inside or outside the container.
<button class="prev" aria-label="Previous testimonial">❮</button>
<button class="next" aria-label="Next testimonial">❯</button>
.prev, .next {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0,0,0,0.5);
color: white;
border: none;
padding: 0.75rem;
cursor: pointer;
z-index: 10;
}
.prev { left: 0; border-radius: 0 4px 4px 0; }
.next { right: 0; border-radius: 4px 0 0 4px; }
document.querySelector('.prev').addEventListener('click', () => {
const prev = (currentIndex - 1 + slides.length) % slides.length;
showSlide(prev);
});
document.querySelector('.next').addEventListener('click', nextSlide);
Fetching Testimonials from Directus
In a headless CMS like Directus, testimonial data is stored in a collection (e.g., Testimonials) with fields for quote, client_name, client_title, and optionally an image. Using the Directus JavaScript SDK or plain fetch, retrieve and render the data dynamically.
async function fetchTestimonials() {
try {
const response = await fetch('https://your-project.directus.app/items/Testimonials');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
return data.data;
} catch (error) {
console.error('Failed to fetch testimonials:', error);
return [];
}
}
async function renderSlider() {
const testimonials = await fetchTestimonials();
const sliderContainer = document.getElementById('testimonial-slider');
const navContainer = document.querySelector('.slider-nav');
// Clear existing content
sliderContainer.innerHTML = '';
navContainer.innerHTML = '';
testimonials.forEach((item, index) => {
const slide = document.createElement('div');
slide.className = `slide${index === 0 ? ' active' : ''}`;
slide.innerHTML = `
<blockquote>
<p>“${item.quote}”</p>
<footer>— ${item.client_name}${item.client_title ? `, ${item.client_title}` : ''}</footer>
</blockquote>
`;
sliderContainer.appendChild(slide);
const dot = document.createElement('button');
dot.className = index === 0 ? 'active-dot' : '';
dot.setAttribute('aria-label', `Go to testimonial ${index + 1}`);
dot.addEventListener('click', () => {
stopAutoPlay();
showSlide(index);
startAutoPlay(4000);
});
navContainer.appendChild(dot);
});
// Re‑initialize variables and start
slides = document.querySelectorAll('.slide');
dots = document.querySelectorAll('.slider-nav button');
showSlide(0);
startAutoPlay();
}
This approach keeps your testimonials centralized in Directus and updates the slider automatically whenever content changes. For better performance, consider caching the API response or using the Directus SDK with query parameters to filter or sort testimonials.
Accessibility Considerations
An inclusive slider ensures all users can interact with your testimonials. Key practices include:
- Setting
role="region"on the slider container andaria-roledescription="carousel"(with fallback for screen readers). - Adding
aria-labelto control buttons (e.g., “Next testimonial”, “Previous testimonial”). - Using
aria-hiddenon non‑visible slides. - Pausing autoplay on focus or hover, and providing a play/pause button.
- Ensuring keyboard navigation: arrow keys move between slides, and tab focus is managed within the widget.
For a deep dive, refer to the WAI Web Accessibility Tutorials on Carousels.
Responsive Design & Touch Support
Testimonial sliders must work across mobile devices, tablets, and desktops. Use relative units and media queries to adjust padding, font size, and navigation layout.
@media (max-width: 600px) {
.slide {
padding: 1.25rem;
}
blockquote {
font-size: 0.95rem;
}
.slider-nav button {
width: 10px;
height: 10px;
}
}
For touch support, implement a swipe mechanism using touchstart and touchend event listeners. Track the delta and trigger nextSlide() or prevSlide() based on swipe direction.
let touchStartX = 0;
let touchEndX = 0;
sliderContainer.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
});
sliderContainer.addEventListener('touchend', (e) => {
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
});
function handleSwipe() {
const diff = touchStartX - touchEndX;
if (Math.abs(diff) > 50) {
if (diff > 0) nextSlide();
else {
const prev = (currentIndex - 1 + slides.length) % slides.length;
showSlide(prev);
}
}
}
Performance Optimization
To ensure a smooth user experience, follow these guidelines:
- Lazy‑load any images (e.g., client headshots) using
loading="lazy". - Limit the number of DOM manipulations. When rebuilding the slider from API data, use a document fragment to batch insert slides.
- Throttle or debounce resize event handlers to avoid layout thrashing.
- Minimize JavaScript dependencies – the slider requires no jQuery or heavy libraries.
- Use
requestAnimationFramefor smooth animations if using CSS transitions.
For an in‑depth look at browser rendering optimization, see the Google Web Fundamentals guide on rendering.
Integrating with a Build Process
When using Directus as a headless CMS, your slider code may be part of a larger front‑end project built with a bundler like Webpack, Vite, or Parcel. Keep your JavaScript and CSS in separate files and import them into your main application. For a static site or WordPress theme, you can inline the code directly after the API call.
If you are using the Directus JavaScript SDK, authentication and error handling become simpler:
import { createDirectus, rest, readItems } from '@directus/sdk';
const client = createDirectus('https://your-project.directus.app').with(rest());
async function getTestimonials() {
const testimonials = await client.request(readItems('Testimonials', {
sort: ['sort'],
limit: 10
}));
return testimonials;
}
This approach also gives you type safety if using TypeScript.
Testing Your Slider
After development, test across multiple browsers (Chrome, Firefox, Safari, Edge) and devices. Verify that keyboard controls work, that autoplay pauses on hover, and that the slider falls back gracefully when JavaScript is disabled. Use the browser’s DevTools to simulate slow network speeds and confirm that testimonials load progressively.
Automated testing can be added with tools like Cypress or Playwright to ensure interactions remain reliable as the codebase evolves.
Conclusion
Building a custom testimonial slider with JavaScript gives you the freedom to design a unique, accessible, and performant component that integrates natively with Directus. By structuring the HTML semantically, styling with clean CSS, implementing robust JavaScript, and fetching live data from Directus, you create a dynamic feature that builds trust with your audience. The techniques covered here – dot navigation, arrow controls, touch support, accessibility, and performance – form the foundation for any content carousel you might need in the future.