XDSCarousel@xds/core · Carousel
Preview coming soon
Usage
Carousel scrolls a row of items horizontally when they overflow the available width. Use it for card grids, image galleries, product lists, or any set of items that should be browsable without taking up the full page.Best practices
| Guidance | Practices |
|---|---|
| Do | Enable scroll-snap when each item should land precisely at the start edge, like a gallery or product list. |
| Do | Always provide an aria-label that describes what the carousel contains, like "Featured products" or "Team members". |
| Do | Use a consistent gap and item width so the carousel looks intentional, not like content overflowing by accident. |
| Don't | Use a carousel for content every user must see — not everyone scrolls horizontally. Put critical content above the fold. |
| Don't | Auto-advance items — let the user scroll at their own pace. |
| Don't | Nest carousels — a carousel inside a carousel is confusing and breaks keyboard navigation. |
Anatomy
| Element | Description | |
|---|---|---|
| Scroll container | required | The horizontal overflow area that holds all items. |
| Items | required | The children rendered in a row. Each item is animated with a scroll-driven scale effect. |
| Fade edges | required | Gradient fades on the left and right edges that indicate more content is available. |
| Navigation buttons | Prev/next buttons that appear on hover. Enabled by default, disable with hasButtons={false}. |
Import
tsimport {XDSCarousel} from '@xds/core/Carousel'
Props
| Prop | Type | Description |
|---|---|---|
childrenrequired | ReactNode | Carousel items rendered in a horizontal scroll container. |
gap | 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 (default: 1) | Gap between items using the spacing token scale. |
hasButtons | boolean (default: true) | Show prev/next navigation buttons on hover (desktop only). |
hasSnap | boolean (default: false) | Enable scroll-snap so each child snaps to the start edge. |
aria-label | string (default: 'Carousel') | Accessible label for the carousel region. |
ref | React.Ref<HTMLDivElement> | Ref forwarded to the root element. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value. |
className | string | CSS class name for the root element. Prefer xstyle for styling. |
style | CSSProperties | Inline styles for the root element. Prefer xstyle. |
data-testid | string | Test selector for automated testing frameworks. |
Examples
Common configurations, variations, and states.Carousel — Cards
tsx'use client';import {XDSCarousel} from '@xds/core/Carousel';import {XDSCard} from '@xds/core/Card';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';const FEATURES = [{title: 'Design System',desc: 'Tokens, components, and patterns',},{title: 'Documentation',desc: 'API reference and usage guides',},{title: 'Sandbox',desc: 'Visual testing and previews',},{title: 'Library',desc: 'Component and hook information',},{title: 'Contributing',desc: 'Open source development',},];export default function CarouselCards() {return (<XDSStack direction="vertical" gap={3} style={{maxWidth: 520, padding: 8}}><XDSText type="body" weight="bold">Browse features</XDSText><XDSCarousel gap={2} hasSnap aria-label="Feature cards">{FEATURES.map(item => (<XDSCard key={item.title} width={200} minHeight={100}><XDSStack direction="vertical" gap={1}><XDSText type="body" weight="bold">{item.title}</XDSText><XDSText type="supporting" color="secondary">{item.desc}</XDSText></XDSStack></XDSCard>))}</XDSCarousel></XDSStack>);}
Carousel — SnapScroll-snap carousel with navigation buttons and team member cards. Each card snaps to the start edge on scroll. Use when items should be viewed one at a time rather than as a continuous strip.
tsx'use client';import {XDSCarousel} from '@xds/core/Carousel';import {XDSAvatar} from '@xds/core/Avatar';import {XDSBadge} from '@xds/core/Badge';import {XDSCard} from '@xds/core/Card';import {XDSStack} from '@xds/core/Layout';import {XDSText} from '@xds/core/Text';const TEAM = [{name: 'Alice Chen', role: 'Engineering Lead', color: 'blue' as const},{name: 'Bob Smith', role: 'Product Designer', color: 'purple' as const},{name: 'Carol Davis', role: 'Product Manager', color: 'green' as const},{name: 'Andrew Thomas', role: 'Design Manager', color: 'red' as const},{name: 'Gina Wilson', role: 'Software Engineer', color: 'orange' as const},];export default function CarouselSnap() {return (<XDSStack direction="vertical" gap={3} style={{maxWidth: 520, padding: 8}}><XDSText type="body" weight="bold">Team members</XDSText><XDSCarousel gap={2} hasSnap hasButtons aria-label="Team members">{TEAM.map(person => (<XDSCard key={person.name} width={180} minHeight={140}><XDSStack direction="vertical" gap={3} hAlign="center"><XDSAvatar name={person.name} size="medium" /><XDSStack direction="vertical" gap={1} hAlign="center"><XDSText type="body" weight="bold">{person.name}</XDSText><XDSBadge variant={person.color} label={person.role} /></XDSStack></XDSStack></XDSCard>))}</XDSCarousel></XDSStack>);}
Showcase source
tsx'use client';import {XDSCarousel} from '@xds/core/Carousel';import {XDSCard} from '@xds/core/Card';import {XDSStack} from '@xds/core/Layout';import {XDSText, XDSHeading} from '@xds/core/Text';import * as stylex from '@stylexjs/stylex';const styles = stylex.create({root: {maxWidth: 500,},card: {minWidth: 200,},});const ITEMS = [{title: 'Design', body: 'Create wireframes and prototypes.'},{title: 'Develop', body: 'Build components and pages.'},{title: 'Test', body: 'Write tests and fix bugs.'},{title: 'Deploy', body: 'Ship to production.'},{title: 'Monitor', body: 'Track performance and errors.'},];export default function CarouselShowcase() {return (<XDSCarouselgap={2}hasSnaparia-label="Workflow steps"xstyle={styles.root}>{ITEMS.map(item => (<XDSCard key={item.title} padding={3} xstyle={styles.card}><XDSStack direction="vertical" gap={1}><XDSHeading level={5}>{item.title}</XDSHeading><XDSText type="supporting" color="secondary">{item.body}</XDSText></XDSStack></XDSCard>))}</XDSCarousel>);}