Featured projects
"use client";
import { CarouselRail, Stack, Text } from "@vortexlabs/vortex";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailExample = () => {
return (
<Stack width="100%" maxWidth="1024px">
<CarouselRail ariaLabel="Featured projects">
<Stack gap={6}>
<Stack direction="row" align="center" justify="space-between" gap={4}>
<Stack gap={1}>
<Text as="h2" size="body-xl-desktop" weight="medium">
Featured projects
</Text>
<Text size="body-sm-desktop" color="subtle">
Recently updated work from your team.
</Text>
</Stack>
<CarouselRail.Controls
previousAriaLabel="Previous projects"
nextAriaLabel="Next projects"
size="lg"
/>
</Stack>
<CarouselRail.Viewport>
{slides.map((value) => (
<CarouselRail.Item key={value} value={value} width="300px">
<Stack
height="260px"
backgroundColor="surface"
borderColor="default"
borderRadius="md"
/>
</CarouselRail.Item>
))}
</CarouselRail.Viewport>
</Stack>
</CarouselRail>
</Stack>
);
};Features
- Supports responsive item widths, alignment, and navigation groups.
- Offers swipe navigation and external or overlay arrows.
- Supports controlled selection, autoplay, and lazy mounting.
- Supports RTL layouts and reduced-motion preferences.
Anatomy
<CarouselRail ariaLabel="Featured projects">
<CarouselRail.Controls previousAriaLabel="Previous projects" nextAriaLabel="Next projects" />
<CarouselRail.Viewport>
<CarouselRail.Item value="atlas" width="300px">Atlas</CarouselRail.Item>
<CarouselRail.Item value="nova" width="300px">Nova</CarouselRail.Item>
</CarouselRail.Viewport>
</CarouselRail>value.Responsive Items
itemsPerView sets how many items fit, and itemsPerScroll how many each step moves (a full view by default). Both are responsive. For mixed widths, set width on each item instead.
"use client";
import { CarouselRail, Placeholder, Stack } from "@vortexlabs/vortex";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailResponsiveExample = () => {
return (
<CarouselRail
ariaLabel="Responsive items"
itemsPerView={{ xs: 1, sm: 2, md: 3 }}
itemsPerScroll={{ xs: 1, md: 3 }}
>
<Stack gap={3}>
<Stack direction="row" justify="end">
<CarouselRail.Controls previousAriaLabel="Previous slides" nextAriaLabel="Next slides" />
</Stack>
<CarouselRail.Viewport gap={{ xs: 2, md: 4 }} borderRadius={{ xs: "sm", md: "lg" }}>
{slides.map((value) => (
<CarouselRail.Item key={value} value={value}>
<Placeholder height="180px" minWidth={0} borderRadius={{ xs: "sm", md: "lg" }}>
Slide {value}
</Placeholder>
</CarouselRail.Item>
))}
</CarouselRail.Viewport>
</Stack>
</CarouselRail>
);
};Alignment
align snaps the selected item to the start (default), center, or end.
"use client";
import { CarouselRail, Placeholder, Stack } from "@vortexlabs/vortex";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailAlignExample = () => {
return (
<CarouselRail ariaLabel="Centered cards" align="center" defaultValue="3" itemsPerScroll={1}>
<Stack gap={3}>
<Stack direction="row" justify="end">
<CarouselRail.Controls previousAriaLabel="Previous card" nextAriaLabel="Next card" />
</Stack>
<CarouselRail.Viewport>
{slides.map((value) => (
<CarouselRail.Item key={value} value={value} width="240px">
<Placeholder height="180px" minWidth={0}>
Card {value}
</Placeholder>
</CarouselRail.Item>
))}
</CarouselRail.Viewport>
</Stack>
</CarouselRail>
);
};Controls
Place Controls anywhere in your layout, or set position="overlay" to float them over the viewport. arrows="hover" shows them on hover or focus.
"use client";
import { CarouselRail, Placeholder } from "@vortexlabs/vortex";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailOverlayExample = () => {
return (
<CarouselRail ariaLabel="Overlay controls" itemsPerView={3}>
<CarouselRail.Viewport>
{slides.map((value) => (
<CarouselRail.Item key={value} value={value}>
<Placeholder height="180px" minWidth={0}>
Slide {value}
</Placeholder>
</CarouselRail.Item>
))}
<CarouselRail.Controls
previousAriaLabel="Previous slides"
nextAriaLabel="Next slides"
position="overlay"
arrows="hover"
rounded
/>
</CarouselRail.Viewport>
</CarouselRail>
);
};Lazy Mounting
lazyMount mounts items as users reach them and keeps them mounted, so state like these notes survives. It needs itemsPerView or an item width. Give placeholder the content's height.
"use client";
import { CarouselRail, Placeholder, Stack, TextField } from "@vortexlabs/vortex";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailLazyExample = () => {
return (
<CarouselRail ariaLabel="Lazy mounting" itemsPerView={1} itemsPerScroll={1} lazyMount>
<Stack gap={3}>
<Stack direction="row" justify="end">
<CarouselRail.Controls previousAriaLabel="Previous slides" nextAriaLabel="Next slides" />
</Stack>
<CarouselRail.Viewport>
{slides.map((value) => (
<CarouselRail.Item
key={value}
value={value}
placeholder={
<Placeholder height="180px" minWidth={0}>
Not mounted yet
</Placeholder>
}
>
<Placeholder height="180px" minWidth={0}>
<TextField.Root>
<TextField.Label>Notes for slide {value}</TextField.Label>
<TextField.Input
name={`notes-${value}`}
placeholder="Type a note, then navigate away and back"
/>
</TextField.Root>
</Placeholder>
</CarouselRail.Item>
))}
</CarouselRail.Viewport>
</Stack>
</CarouselRail>
);
};Controlled Selection
Control selection with value and onChange; defaultValue only sets the start. With a numeric itemsPerScroll, onChange reports the group's first item.
"use client";
import { Button, CarouselRail, Placeholder, Stack, Text } from "@vortexlabs/vortex";
import { useState } from "react";
const slides = ["1", "2", "3", "4", "5", "6"];
export const CarouselRailControlledExample = () => {
const [value, setValue] = useState("1");
return (
<Stack gap={3}>
<Stack direction="row" gap={2} wrap>
{slides.map((slide) => (
<Button
key={slide}
size="sm"
variant={value === slide ? "primary" : "secondary"}
onClick={() => setValue(slide)}
attributes={{ "aria-pressed": value === slide }}
>
Slide {slide}
</Button>
))}
</Stack>
<CarouselRail
ariaLabel="Controlled carousel"
itemsPerView={1}
itemsPerScroll={1}
value={value}
onChange={({ value }) => setValue(value)}
>
<Stack gap={3}>
<Stack direction="row" justify="end">
<CarouselRail.Controls
previousAriaLabel="Previous slides"
nextAriaLabel="Next slides"
/>
</Stack>
<CarouselRail.Viewport>
{slides.map((value) => (
<CarouselRail.Item key={value} value={value}>
<Placeholder height="180px" minWidth={0}>
Slide {value}
</Placeholder>
</CarouselRail.Item>
))}
</CarouselRail.Viewport>
</Stack>
</CarouselRail>
<Text>Selected slide: {value}</Text>
</Stack>
);
};For one item at a time, use CarouselGallery. autoPlay works as in Carousel Gallery.
Accessibility
ariaLabel, and the arrows with previousAriaLabel and nextAriaLabel.playLabel, pauseLabel, and renderStatusText in non-English apps; their defaults are English.