Carousel
Swipeable, paginated slide container built on Embla — for hero sections, galleries, and feature tours.
Slides expose roving focus; previous/next controls are real buttons with aria-labels; ArrowLeft/Right (or Up/Down) navigates slides.
Preview
tsx
import {
Carousel,
CarouselContainer,
CarouselDots,
CarouselNext,
CarouselPrevious,
CarouselSlide,
CarouselViewport,
} from '@arshad-shah/cynosure-react';
const slides = [
{ id: 1, title: 'Ship faster', body: 'Composable primitives for modern UIs.' },
{ id: 2, title: 'Design tokens', body: 'Themeable with first-class dark mode.' },
{ id: 3, title: 'Accessible', body: 'WAI-ARIA patterns built in.' },
];
export default function Example() {
return (
<div style={{ width: '32rem', maxWidth: '100%' }}>
<Carousel>
<CarouselViewport>
<CarouselContainer>
{slides.map((s) => (
<CarouselSlide key={s.id}>
<div
style={{
aspectRatio: '16 / 9',
display: 'grid',
placeItems: 'center',
padding: '1.5rem',
background: 'var(--cynosure-color-surface-2)',
borderRadius: 'var(--cynosure-radius-lg)',
}}
>
<div style={{ textAlign: 'center' }}>
<h3 style={{ margin: 0 }}>{s.title}</h3>
<p style={{ margin: '0.5rem 0 0', color: 'var(--cynosure-color-fg-muted)' }}>
{s.body}
</p>
</div>
</div>
</CarouselSlide>
))}
</CarouselContainer>
</CarouselViewport>
<CarouselPrevious />
<CarouselNext />
<CarouselDots />
</Carousel>
</div>
);
}
Slides per view
Section titled “Slides per view”Pass a number to show several slides at once. Use a responsive object (base, sm, md, lg, xl) for breakpoint-aware gallery layouts.
Preview
tsx
import {
Carousel,
CarouselContainer,
CarouselNext,
CarouselPrevious,
CarouselSlide,
CarouselViewport,
} from '@arshad-shah/cynosure-react';
const items = Array.from({ length: 8 }, (_, i) => i + 1);
export default function Example() {
return (
<div style={{ width: '36rem', maxWidth: '100%' }}>
<Carousel slidesPerView={{ base: 1, sm: 2, md: 3 }} align="start">
<CarouselViewport>
<CarouselContainer>
{items.map((n) => (
<CarouselSlide key={n} style={{ paddingInline: '0.25rem' }}>
<div
style={{
aspectRatio: '1 / 1',
display: 'grid',
placeItems: 'center',
background: 'var(--cynosure-color-surface-2)',
borderRadius: 'var(--cynosure-radius-md)',
fontWeight: 600,
}}
>
Card {n}
</div>
</CarouselSlide>
))}
</CarouselContainer>
</CarouselViewport>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
}
Set loop to wrap from the last slide back to the first. The previous/next buttons stay enabled at the bounds when looping is active.
Preview
tsx
import {
Carousel,
CarouselContainer,
CarouselDots,
CarouselNext,
CarouselPrevious,
CarouselSlide,
CarouselViewport,
} from '@arshad-shah/cynosure-react';
const slides = ['Alpha', 'Bravo', 'Charlie', 'Delta'];
export default function Example() {
return (
<div style={{ width: '28rem', maxWidth: '100%' }}>
<Carousel loop>
<CarouselViewport>
<CarouselContainer>
{slides.map((label) => (
<CarouselSlide key={label}>
<div
style={{
aspectRatio: '16 / 9',
display: 'grid',
placeItems: 'center',
background: 'var(--cynosure-color-surface-2)',
borderRadius: 'var(--cynosure-radius-lg)',
fontSize: '1.25rem',
fontWeight: 600,
}}
>
{label}
</div>
</CarouselSlide>
))}
</CarouselContainer>
</CarouselViewport>
<CarouselPrevious />
<CarouselNext />
<CarouselDots />
</Carousel>
</div>
);
}
Orientation
Section titled “Orientation”Pass orientation="vertical" for a stacked carousel; ArrowUp / ArrowDown navigate between slides.
Preview
tsx
import {
Carousel,
CarouselContainer,
CarouselNext,
CarouselPrevious,
CarouselSlide,
CarouselViewport,
} from '@arshad-shah/cynosure-react';
const slides = ['Inbox', 'Drafts', 'Sent', 'Archive'];
export default function Example() {
return (
<div style={{ width: '20rem' }}>
<Carousel orientation="vertical" style={{ height: 240 }}>
<CarouselViewport style={{ height: '100%' }}>
<CarouselContainer style={{ flexDirection: 'column', height: '100%' }}>
{slides.map((label) => (
<CarouselSlide key={label}>
<div
style={{
height: '100%',
display: 'grid',
placeItems: 'center',
background: 'var(--cynosure-color-surface-2)',
borderRadius: 'var(--cynosure-radius-md)',
fontWeight: 600,
}}
>
{label}
</div>
</CarouselSlide>
))}
</CarouselContainer>
</CarouselViewport>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
}
PropTypeDefaultDescription
slidesPerView
number|ResponsiveSlides
1
How many slides fit in the viewport. `1` is a classic full-bleed carousel;
larger values create a "peek" layout for gallery/card decks.
Accepts a number or a responsive `{ base, sm, md, lg, xl }` object.
orientation
"horizontal"|"vertical"
horizontal
Scroll axis. `horizontal` maps to Embla's `x` axis, `vertical` to `y`.
loop
boolean
false
Loop back to the first slide after the last (and vice versa).
align
"start"|"center"|"end"
start
Where each slide snaps inside the viewport.
slidesToScroll
number|"auto"
1
How many slides advance per next/prev. `auto` snaps a full viewport's
worth at a time.
draggable
boolean
true
Enable drag-to-scroll via pointer/touch.
options
Partial<OptionsType>
—
Full Embla options bag — overrides any of the convenience props above.
plugins
CreatePluginType<LoosePluginType, {}>[]
—
Embla plugins (autoplay, auto-scroll, class-name toggle, etc.).
setApi
((api: EmblaCarouselType) => void)
—
Fires once Embla initialises with the live API; use to drive external UI.
onSelect
((index: number) => void)
—
Fires whenever the selected slide index changes (initial + on user/programmatic scroll).
children
ReactNode
—
Carousel parts ({@link CarouselViewport}, controls, dots, …).
Accessibility
Section titled “Accessibility”- The root region exposes
aria-roledescription="carousel". - Each
CarouselSlidecarriesrole="group"+aria-roledescription="slide". CarouselPrevious/CarouselNextare real<button>elements with descriptivearia-labels and are disabled at the bounds whenloopis off.- Pagination dots use
role="tablist"/role="tab"and reflect the active slide viaaria-selected. - Arrow keys on the root scroll between slides —
ArrowLeft/ArrowRighthorizontally,ArrowUp/ArrowDownvertically.
Recipes
Section titled “Recipes”- Pass
slidesPerView={{ base: 1, sm: 2, lg: 3 }}for responsive multi-slide layouts. - Use the
pluginsprop to add Embla extensions likeembla-carousel-autoplay. - Pass a custom
icontoCarouselPrevious/CarouselNextto replace the default chevrons. - Use
setApito capture the Embla API for external “go to slide N” buttons or scroll-progress UI.