awesome-cube-carousel
v0.1.1
Published
A stunning 3D cube carousel React component with CSS3 perspective transitions, per-slide accent colours, autoplay, and a sci-fi-inspired HUD chrome.
Maintainers
Readme
awesome-cube-carousel
A stunning 3D cube carousel React component with smooth CSS3 perspective transitions, per-slide accent colours, ambient glow effects, a scanline overlay, a starfield background, and a sci-fi HUD chrome — all with zero external dependencies beyond React itself.
Features
- True 3D CSS cube — 6-face box with
preserve-3dandperspective, no canvas or WebGL required - 4-direction rotation sequence — Right→Left, Left→Right, Top→Bottom, Bottom→Top cycling automatically
- Per-slide accent colour — every slide has its own hex accent that drives glow, gradients, edge pulses, and the progress bar
- Background image support — optional image blended in luminosity mode over the grid background
- Autoplay with progress bar — configurable interval with a smooth bottom progress bar
- Direction indicator HUD — animated arrow, label, and sequence step dots
- Slide info strip — zero-padded counter and title below the cube
- Navigation dots — pill-shaped dots expanding to show the active slide
- Ambient starfield — 90 subtly blinking stars behind the cube
- ESM + CJS dual build — works in Vite, CRA, Next.js, and any modern bundler
- Full TypeScript types included
Installation
npm install awesome-cube-carousel
# or
yarn add awesome-cube-carousel
# or
pnpm add awesome-cube-carouselPeer dependencies — React 17+ and ReactDOM 17+ must already be installed in your project.
Importing Styles
The component uses CSS animations (scanline, fadeUp, edgePulse, dotBlink, progressBar) and CSS custom properties (--font-display, --font-body) defined in a separate stylesheet. You must import it once in your app:
import 'awesome-cube-carousel/style.css';The stylesheet also expects two Google Fonts to be loaded for the full visual effect. Add these to your index.html <head>:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Outfit:wght@300;400;600&display=swap"
rel="stylesheet"
/>If you skip the fonts the component will still render correctly — it will fall back to
sans-serif.
🚀 Quick Start
import { AwesomeCubeCarousel } from 'awesome-cube-carousel';
import 'awesome-cube-carousel/style.css';
const slides = [
{
title: 'AURORA',
subtitle: 'Northern Lights',
description: 'A breathtaking natural light display in the sky.',
tag: 'Nature',
accent: '#00f5c8',
},
{
title: 'NEBULA',
subtitle: 'Deep Space',
description: 'Clouds of gas and dust where new stars are born.',
tag: 'Space',
accent: '#a855f7',
},
{
title: 'HORIZON',
subtitle: 'Ocean View',
description: 'Where the sea meets the endless sky at dawn.',
tag: 'Landscape',
accent: '#f97316',
},
];
export default function App() {
return <AwesomeCubeCarousel slides={slides} />;
}📐 Props
| Prop | Type | Default | Description |
|---|---|---|---|
| slides | CubeSlide[] | required | Array of slide objects (see below). Minimum 2 slides recommended. |
| size | number | 380 | Width and height of each cube face in pixels. |
| intervalSeconds | number | 3.5 | Seconds each slide is displayed before advancing. |
| autoPlay | boolean | true | Whether the carousel advances automatically. |
| onSlideChange | (index: number) => void | undefined | Callback fired after each slide transition completes, with the new 0-based index. |
CubeSlide object
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | ✅ | Main heading displayed on the face. |
| subtitle | string | ➖ | Smaller coloured line below the title. |
| description | string | ➖ | Body paragraph visible only on the active face. |
| tag | string | ➖ | Category badge with a glowing dot shown at the top-left. |
| accent | string | ✅ | Hex colour (e.g. '#00f5c8') used for all glows, gradients, and the progress bar. |
| image | string | ➖ | URL of a background image blended over the grid in luminosity mode. |
| onClick | () => void | ➖ | Called when the EXPLORE CTA button on the active face is clicked. |
Examples
Example 1 — Minimal (no images, no callbacks)
The simplest possible usage with three slides and default autoplay.
import { AwesomeCubeCarousel } from 'awesome-cube-carousel';
import 'awesome-cube-carousel/style.css';
const slides = [
{
title: 'VELOCITY',
subtitle: 'Speed redefined',
accent: '#f97316',
},
{
title: 'PRECISION',
subtitle: 'Every detail matters',
accent: '#3b82f6',
},
{
title: 'VISION',
subtitle: 'See what is possible',
accent: '#a855f7',
},
];
export default function MinimalDemo() {
return <AwesomeCubeCarousel slides={slides} />;
}Example 2 — Full featured with images and onSlideChange
Uses all available fields including background images, tags, descriptions, click handlers, a custom cube size, and a slower rotation interval.
import { useState } from 'react';
import { AwesomeCubeCarousel } from 'awesome-cube-carousel';
import 'awesome-cube-carousel/style.css';
const slides = [
{
title: 'ARCTIC',
subtitle: 'Ice & Silence',
description:
'Vast frozen tundra stretching to the horizon, lit only by the polar glow.',
tag: 'Explore',
accent: '#38bdf8',
image: 'https://images.unsplash.com/photo-1517411032315-54ef2cb783bb?w=800',
onClick: () => window.open('https://example.com/arctic', '_blank'),
},
{
title: 'DESERT',
subtitle: 'Heat & Dunes',
description:
'Golden sand sculpted by the wind into endless rolling waves.',
tag: 'Journey',
accent: '#f59e0b',
image: 'https://images.unsplash.com/photo-1509316785289-025f5b846b35?w=800',
onClick: () => window.open('https://example.com/desert', '_blank'),
},
{
title: 'JUNGLE',
subtitle: 'Life & Canopy',
description:
'A cathedral of ancient trees alive with the sound of unseen creatures.',
tag: 'Wildlife',
accent: '#22c55e',
image: 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=800',
onClick: () => window.open('https://example.com/jungle', '_blank'),
},
{
title: 'ABYSS',
subtitle: 'Depth & Mystery',
description:
'The deep ocean floor — one of Earth\'s last unexplored frontiers.',
tag: 'Discovery',
accent: '#6366f1',
image: 'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?w=800',
onClick: () => window.open('https://example.com/abyss', '_blank'),
},
];
export default function FullFeaturedDemo() {
const [current, setCurrent] = useState(0);
return (
<div>
<p style={{ color: '#fff', textAlign: 'center', fontFamily: 'sans-serif' }}>
Viewing slide {current + 1} of {slides.length}: {slides[current].title}
</p>
<AwesomeCubeCarousel
slides={slides}
size={440}
intervalSeconds={5}
autoPlay={true}
onSlideChange={(index) => setCurrent(index)}
/>
</div>
);
}Example 3 — Manual control (autoPlay={false})
Disables autoplay and drives the carousel using external buttons. The onSlideChange callback is used to keep a local counter in sync, while the carousel advances internally when you programmatically manage the slides array order — or simply use it as a display-only component with navigation handled by your own UI.
In this pattern we pass
autoPlay={false}and provide our own Next / Prev buttons that reorder the slides array, letting us own the navigation entirely.
import { useState } from 'react';
import { AwesomeCubeCarousel } from 'awesome-cube-carousel';
import 'awesome-cube-carousel/style.css';
const ALL_SLIDES = [
{
title: 'MERCURY',
subtitle: 'Closest to the Sun',
description: 'The smallest planet, scarred by craters and extreme temperatures.',
tag: 'Planet',
accent: '#e2e8f0',
},
{
title: 'VENUS',
subtitle: 'The Veiled World',
description: 'A toxic atmosphere that traps heat, making it the hottest planet.',
tag: 'Planet',
accent: '#fde68a',
},
{
title: 'MARS',
subtitle: 'The Red Planet',
description: 'Iron-oxide dust coats the surface of this cold, barren world.',
tag: 'Planet',
accent: '#f87171',
},
{
title: 'JUPITER',
subtitle: 'King of Planets',
description: 'A gas giant large enough to swallow all other planets combined.',
tag: 'Planet',
accent: '#fb923c',
},
];
export default function ManualControlDemo() {
const [startIdx, setStartIdx] = useState(0);
// Rotate the array so the desired slide is first — the carousel
// always treats index 0 as the initial active slide on mount.
// For a true controlled carousel you would remount with a key prop.
const [activeLabel, setActiveLabel] = useState(ALL_SLIDES[0].title);
const next = () =>
setStartIdx((i) => (i + 1) % ALL_SLIDES.length);
const prev = () =>
setStartIdx((i) => (i - 1 + ALL_SLIDES.length) % ALL_SLIDES.length);
// Reorder slides so chosen index is first
const slides = [
...ALL_SLIDES.slice(startIdx),
...ALL_SLIDES.slice(0, startIdx),
];
const btnStyle = {
padding: '10px 28px',
background: 'transparent',
border: '1px solid rgba(255,255,255,0.25)',
color: '#fff',
borderRadius: '4px',
cursor: 'pointer',
fontFamily: 'sans-serif',
fontSize: '14px',
letterSpacing: '0.1em',
};
return (
<div style={{ position: 'relative' }}>
<AwesomeCubeCarousel
key={startIdx}
slides={slides}
autoPlay={false}
onSlideChange={(i) => setActiveLabel(slides[i].title)}
/>
{/* Navigation overlay */}
<div
style={{
position: 'absolute',
bottom: 60,
left: '50%',
transform: 'translateX(-50%)',
display: 'flex',
gap: '16px',
zIndex: 50,
}}
>
<button style={btnStyle} onClick={prev}>← PREV</button>
<button style={btnStyle} onClick={next}>NEXT →</button>
</div>
</div>
);
}📄 License
MIT
