Cards Carousel
Responsive image-card carousel with consumer-owned data and optional actions.
Install
One command copies the item's source into your project, where you own and edit it.
npx manteen add @house/cards-carouselDependencies
@mantine/carousel@^9npm@mantine/core@^9npm@mantine/hooks@^9npm
Installing this item also installs @house/mantine-ui-license from the registry.
Preview
Usage
A copy-ready example published by the registry author. Imports use the aliases Manteen configures at init.
import { CardsCarousel, type CardsCarouselItem } from "@/components/ui/cards-carousel";
const items: CardsCarouselItem[] = [ { id: "1", image: "https://images.unsplash.com/photo-1483728642387-6c3bdd6c93e5?auto=format&fit=crop&w=1080&q=80", title: "Best forests to visit in North America", category: "Nature", href: "/articles/best-forests", }, { id: "2", image: "https://images.unsplash.com/photo-1508193638397-1c4234db14d8?auto=format&fit=crop&w=1080&q=80", title: "Hiking gear you actually need", category: "Outdoors", }, { id: "3", image: "https://images.unsplash.com/photo-1516214104703-d870798883c5?auto=format&fit=crop&w=1080&q=80", title: "Planning a coastal road trip", category: "Travel", href: "/articles/coastal-road-trip", },];
export function FeaturedDestinations() { return ( <CardsCarousel items={items} actionLabel="Read more" onSelect={(item) => console.log("selected", item.id)} /> );}Props
Author-documented props, carried verbatim from the registry item.
| Prop | Type | Default | Description |
|---|---|---|---|
items* | readonly CardsCarouselItem[] | — | Provides the ordered set of cards rendered as carousel slides. |
actionLabel | string | "View" | Sets the label shown on each card's action button. |
onSelect | (item: CardsCarouselItem) => void | — | Called when a card's action button is clicked and the item has no href, rendering the button and handling its click. |
slideSize | CarouselProps["slideSize"] | — | Overrides the responsive default slide width (100% below the sm breakpoint, 50% above). |
slidesToScroll | number | — | Overrides how many slides advance per navigation (defaults to 1 below the sm breakpoint, 2 above). |
...others | BoxProps & ElementProps<"div", "onSelect"> | — | All other props are forwarded to the root <Carousel>, after its own slideGap, emblaOptions, and control props, so both can be overridden. Mantine Styles API props (classNames, styles, unstyled) are also accepted. |
* required
Styling
CardsCarousel exposes these parts through the public classNames/styles interface:
rootcardtitlecategory
Author-declared; Manteen reports the declaration but does not verify each selector is wired.
Package-level styles, composed into the managed global stylesheet during install:
@mantine/carousel/styles.css
Source
2 installable files ship with this item.
registry/mantine-ui/cards-carousel/cards-carousel.tsxcomponent · 4.2 kB · tsx
/** * Adapted from Mantine UI's CardsCarousel at * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see * LICENSES/MANTINE-UI.txt after installation. */
import { Carousel, type CarouselProps } from "@mantine/carousel";import { type BoxProps, Button, type ElementProps, type Factory, factory, type GetStylesApi, Paper, type StylesApiProps, Text, Title, useMantineTheme, useProps, useStyles,} from "@mantine/core";import { useMediaQuery } from "@mantine/hooks";
import classes from "./cards-carousel.module.css";
export type CardsCarouselStylesNames = "root" | "card" | "title" | "category";
export interface CardsCarouselItem { id: string; image: string; title: string; category: string; href?: string;}
export interface CardsCarouselProps extends BoxProps, StylesApiProps<CardsCarouselFactory>, ElementProps<"div", "onSelect"> { items: readonly CardsCarouselItem[]; actionLabel?: string; onSelect?: (item: CardsCarouselItem) => void; /** Overrides the responsive default slide width (100% below `sm`, 50% above). */ slideSize?: CarouselProps["slideSize"]; /** Overrides how many slides advance per navigation (default 1 below `sm`, 2 above). */ slidesToScroll?: number;}
export type CardsCarouselFactory = Factory<{ props: CardsCarouselProps; ref: HTMLDivElement; stylesNames: CardsCarouselStylesNames;}>;
interface CarouselItemCardProps { item: CardsCarouselItem; actionLabel: string; onSelect?: (item: CardsCarouselItem) => void; getStyles: GetStylesApi<CardsCarouselFactory>; unstyled: boolean | undefined;}
function CarouselItemCard({ item, actionLabel, onSelect, getStyles, unstyled,}: CarouselItemCardProps) { const action = item.href ? ( <Button component="a" href={item.href} variant="white" color="dark"> {actionLabel} </Button> ) : onSelect ? ( <Button variant="white" color="dark" onClick={() => onSelect(item)}> {actionLabel} </Button> ) : null;
return ( <Paper shadow="md" p="xl" radius="md" unstyled={unstyled} {...getStyles("card", { style: { backgroundImage: `linear-gradient(rgb(0 0 0 / 55%), rgb(0 0 0 / 55%)), url(${item.image})`, }, })} > <div> <Text {...getStyles("category")} size="xs"> {item.category} </Text> <Title order={3} {...getStyles("title")}> {item.title} </Title> </div> {action} </Paper> );}
export const CardsCarousel = factory<CardsCarouselFactory>((_props) => { const props = useProps("CardsCarousel", null, _props); const { classNames, className, style, styles, unstyled, vars, attributes, ref, items, actionLabel = "View", onSelect, slideSize, slidesToScroll, ...others } = props;
const getStyles = useStyles<CardsCarouselFactory>({ name: "CardsCarousel", classes, props, className, style, classNames, styles, unstyled, attributes, vars, });
const theme = useMantineTheme(); const mobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
return ( <Carousel ref={ref} slideSize={slideSize ?? { base: "100%", sm: "50%" }} slideGap={2} emblaOptions={{ align: "start", slidesToScroll: slidesToScroll ?? (mobile ? 1 : 2) }} nextControlProps={{ "aria-label": "Next slide" }} previousControlProps={{ "aria-label": "Previous slide" }} unstyled={unstyled} {...getStyles("root")} {...others} > {items.map((item) => ( <Carousel.Slide key={item.id}> <CarouselItemCard item={item} actionLabel={actionLabel} onSelect={onSelect} getStyles={getStyles} unstyled={unstyled} /> </Carousel.Slide> ))} </Carousel> );});
CardsCarousel.classes = classes;CardsCarousel.displayName = "CardsCarousel";
export namespace CardsCarousel { export type Props = CardsCarouselProps; export type StylesNames = CardsCarouselStylesNames; export type Factory = CardsCarouselFactory;}registry/mantine-ui/cards-carousel/cards-carousel.module.cssstyle · 999 B · css
/* * Adapted from Mantine UI's CardsCarousel at * ffbf61c559f374a7ea28fcf00355e84dcbe9a908. MIT licensed; see * LICENSES/MANTINE-UI.txt after installation. *//* Structural selector only: the Carousel itself owns all default styling for * this element (see @mantine/carousel's own root class). This rule sets no * real CSS (an unused custom property) so the Styles API selector still has * a matching class in this module without introducing a visual default. */.root { --cards-carousel-root: 1;}
.card { display: flex; height: 440px; flex-direction: column; align-items: flex-start; justify-content: space-between; background-position: center; background-size: cover;}
.title { margin-top: var(--mantine-spacing-xs); color: var(--mantine-color-white); font-family: var(--mantine-font-family-headings); font-size: 32px; font-weight: 600; line-height: 1.2;}
.category { color: var(--mantine-color-white); font-weight: 700; text-transform: uppercase;}Author notes
Adapted from Mantine UI CardsCarousel at ffbf61c559…; hardcoded travel data became typed items with links or selection callbacks.