carousel
Carousel
Infinite-loop photo carousel powered by the
carousel() plugin — aligned with
Material Design 3. Items wrap seamlessly with smooth snap-to-item. Switch between
variant layouts to see hero, multi-browse,
uncontained, and full-screen modes.
Photo Carousel
0%
0.00 /
0.00
px/ms
0 /
0
items
variant full
step 1 / 24
Source
<div id="react-root"></div>
// Carousel — Shared data re-exported from curated carousel data
// Each variant uses unique photos with hand-written descriptions
export {
getItems,
getItemCount,
getItemWidth,
getImageUrl,
preloadImages,
} from "../../src/data/carousel.js";
/* Carousel Example — MD3-aligned photo carousel
Common styles (.container, h1, .description, .split-*, .ui-*)
are provided by styles/ui.css and examples/styles.css using design tokens. */
/* ============================================================================
Carousel wrap — container for list + nav + dots
============================================================================ */
.carousel-wrap {
position: relative;
width: 100%;
}
.carousel-wrap--vertical {
width: 300px;
height: 522px;
margin: 0 auto;
}
/* ============================================================================
List container
============================================================================ */
.split-main {
display: flex;
align-items: center;
justify-content: center;
min-height: 520px;
padding: 32px;
}
#list-container {
width: 100%;
height: 360px;
overflow: hidden;
border-radius: 28px;
}
.carousel-wrap--vertical #list-container {
height: 522px;
}
#list-container .vlist {
border: none !important;
border-radius: 28px !important;
background: transparent;
width: 100%;
}
#list-container .vlist-viewport {
width: 100% !important;
}
#list-container .vlist-item {
padding: 0 !important;
border: none !important;
background: transparent;
}
/* ============================================================================
Photo slide — adapts to --vlist-carousel-width
============================================================================ */
.photo-slide {
--vlist-carousel-radius: 28px;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 28px;
background: var(--surface-container);
}
.photo-slide__img {
opacity: 0;
transition: opacity 0.4s ease;
}
.photo-slide__img--loaded {
opacity: 1;
}
.photo-slide__overlay {
padding: 32px 24px 24px;
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0.7) 0%,
rgba(0, 0, 0, 0.3) 60%,
transparent 100%
);
display: flex;
flex-direction: column;
gap: 4px;
transition: opacity 0.15s ease;
}
.photo-slide__title {
font-size: 20px;
font-weight: var(--fw-bold, 700);
color: #fff;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.photo-slide__location {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
/* ============================================================================
Navigation arrows
============================================================================ */
.carousel-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
width: 48px;
height: 48px;
border-radius: 50%;
border: none;
background: rgba(0, 0, 0, 0.45);
color: #fff;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
transition:
background 0.2s,
opacity 0.2s,
transform 0.15s;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
opacity: 0;
}
.carousel-wrap:hover .carousel-nav {
opacity: 1;
}
.carousel-nav:hover {
background: rgba(0, 0, 0, 0.65);
transform: translateY(-50%) scale(1.08);
}
.carousel-nav:active {
transform: translateY(-50%) scale(0.95);
}
.carousel-nav--prev {
left: 16px;
}
.carousel-nav--next {
right: 16px;
}
.carousel-wrap--vertical .carousel-nav {
display: none;
}
/* ============================================================================
Dot indicators
============================================================================ */
.carousel-dots {
display: flex;
justify-content: center;
gap: 8px;
padding: 16px 0 4px;
flex-wrap: wrap;
cursor: pointer;
}
.carousel-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted, #666);
opacity: 0.4;
pointer-events: none;
transition:
opacity 0.2s,
transform 0.2s,
background 0.2s;
}
.carousel-dot:hover {
opacity: 0.7;
transform: scale(1.3);
}
.carousel-dot--active {
opacity: 1;
background: var(--accent, #667eea);
transform: scale(1.3);
}
.carousel-wrap--vertical .carousel-dots {
flex-direction: column;
position: absolute;
right: -24px;
top: 50%;
transform: translateY(-50%);
padding: 0;
}
/* ============================================================================
Photo detail (panel)
============================================================================ */
.photo-detail {
display: flex;
flex-direction: column;
gap: 8px;
}
.photo-detail__frame {
padding: 16px;
border-radius: 16px;
background: var(--surface-container, #e8e8f0);
}
.photo-detail__img {
width: 100%;
aspect-ratio: 1;
object-fit: contain;
border-radius: 12px;
}
.photo-detail__meta {
display: flex;
flex-direction: column;
gap: 2px;
}
.photo-detail__meta strong {
font-weight: var(--fw-bold, 700);
font-size: 14px;
}
.photo-detail__meta span {
font-size: 12px;
color: var(--text-muted, #888);
}
/* ============================================================================
Variant button group wrap
============================================================================ */
.ui-btn-group--wrap {
flex-wrap: wrap;
}