Skip to content

Cards Carousel

ComponentMantine ≥ 9MantineProvider

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.

Terminal window
npx manteen add @house/cards-carousel

Dependencies

  • @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.

cards-carousel.usage.tsx
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.

PropTypeDefaultDescription
items*readonly CardsCarouselItem[]Provides the ordered set of cards rendered as carousel slides.
actionLabelstring"View"Sets the label shown on each card's action button.
onSelect(item: CardsCarouselItem) => voidCalled when a card's action button is clicked and the item has no href, rendering the button and handling its click.
slideSizeCarouselProps["slideSize"]Overrides the responsive default slide width (100% below the sm breakpoint, 50% above).
slidesToScrollnumberOverrides how many slides advance per navigation (defaults to 1 below the sm breakpoint, 2 above).
...othersBoxProps & 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:

  • root
  • card
  • title
  • category

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.