All components
Carousel
Sliding panels with previous and next controls and clickable indicator dots.
123456789101112131415161718192021222324252627282930import { Carousel } from "#components/ui/ark-ui/carousel.tsx";import { indigoPalette } from "@stylexblocks/ui/themes/palettes.ts";
const slides = ["Mountains", "Coastline", "Forest"];
export function Example() { return ( // the real Ark Carousel machine wearing the recipe styles — it owns // page state, drag behavior, and aria; the active dot fills from the // palette automatically <Carousel.Root slideCount={slides.length} allowMouseDrag palette={indigoPalette}> <Carousel.ItemGroup> {slides.map((label, index) => ( <Carousel.Item key={label} index={index}> {label} </Carousel.Item> ))} </Carousel.ItemGroup> <Carousel.Control> <Carousel.PrevTrigger>‹</Carousel.PrevTrigger> <Carousel.IndicatorGroup> {slides.map((label, index) => ( <Carousel.Indicator key={label} index={index} /> ))} </Carousel.IndicatorGroup> <Carousel.NextTrigger>›</Carousel.NextTrigger> </Carousel.Control> </Carousel.Root> );}Install
1npx stylexblocks add carouselSource
components/ui/ark-ui/carousel.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152import * as stylex from "@stylexjs/stylex";import { Carousel as ArkCarousel, useCarouselContext } from "@ark-ui/react/carousel";import type { StyleOverrideProps } from "#lib/recipe-props.ts";import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const carouselRoot = stylex.create({ base: { position: 'relative', display: 'flex', gap: spacing._2, },})
const carouselItem = stylex.create({ base: {},})
const carouselControl = stylex.create({ base: { display: 'flex', alignItems: 'center', },})
const carouselIndicatorGroup = stylex.create({ base: { display: 'flex', justifyContent: 'center', gap: spacing._3, },})
const carouselIndicator = stylex.create({ base: { width: sizes._2_5, height: sizes._2_5, borderRadius: radii.full, backgroundColor: colorPalette.subtle, cursor: cursor.button, },})
// The recipe styles the shell; stacking direction, full-width slides, and// the active-dot accent are the component's own structural styles.const carouselLocal = stylex.create({ root: { flexDirection: "column" }, itemGroup: { width: "100%" }, indicatorCurrent: { backgroundColor: colorPalette.solid },});
/** * Carousel — the real Ark UI Carousel machine wearing the recipe styles. * The machine owns the page state, drag/scroll behavior, and aria semantics * (`slideCount`, `defaultPage`, `page`, `onPageChange`, `allowMouseDrag`); * the active indicator dot fills with the palette's solid color. */function mergeClassName(base: string | undefined, extra: unknown) { return typeof extra === "string" && extra.length > 0 ? [base, extra].filter(Boolean).join(" ") : base;}
// recipe maps hold stylex.props-compatible values by construction; one cast// bridges StyleX's variadic genericsfunction sx( styles: ReadonlyArray<unknown>, palette: StyleOverrideProps["palette"], style: StyleOverrideProps["style"],) { return stylex.props( ...([palette, ...styles, style] as unknown as ReadonlyArray<never>), );}
/** Wraps a machine part with its slot's recipe styles. */function withPart<P extends { className?: string; style?: React.CSSProperties }>( Part: React.ComponentType<P>, styles: ReadonlyArray<unknown>, displayName: string,) { function StyledPart(allProps: Omit<P, "style"> & StyleOverrideProps) { const { palette, style, className, ...rest } = allProps as P & StyleOverrideProps & Record<string, unknown>; const p = sx(styles, palette, style as never); return ( <Part {...(rest as unknown as P)} className={mergeClassName(p.className, className)} style={p.style} /> ); } StyledPart.displayName = displayName; return StyledPart;}
export type CarouselIndicatorProps = Omit< React.ComponentProps<typeof ArkCarousel.Indicator>, "style"> & StyleOverrideProps;
function Indicator(allProps: CarouselIndicatorProps) { const { palette, style, className, ...rest } = allProps as CarouselIndicatorProps & Record<string, unknown>; const carousel = useCarouselContext(); const p = sx( [ carouselIndicator.base, carousel.page === allProps.index && carouselLocal.indicatorCurrent, ], palette, style as never, ); return ( <ArkCarousel.Indicator {...(rest as React.ComponentProps<typeof ArkCarousel.Indicator>)} className={mergeClassName(p.className, className)} style={p.style} /> );}Indicator.displayName = "CarouselIndicator";
export const Carousel = { Root: withPart( ArkCarousel.Root, [carouselRoot.base, carouselLocal.root], "CarouselRoot", ), ItemGroup: withPart( ArkCarousel.ItemGroup, [carouselLocal.itemGroup], "CarouselItemGroup", ), Item: withPart(ArkCarousel.Item, [carouselItem.base], "CarouselItem"), Control: withPart(ArkCarousel.Control, [carouselControl.base], "CarouselControl"), PrevTrigger: withPart(ArkCarousel.PrevTrigger, [], "CarouselPrevTrigger"), NextTrigger: withPart(ArkCarousel.NextTrigger, [], "CarouselNextTrigger"), IndicatorGroup: withPart( ArkCarousel.IndicatorGroup, [carouselIndicatorGroup.base], "CarouselIndicatorGroup", ), Indicator,};