Carousel
A CSS scroll-snap carousel using only Tailwind classes (no JS).
---import Carousel from '@bejamas/ui/components/Carousel.astro';---
<!-- Horizontal images with center snapping using parts --><Carousel class="gap-4 p-2" align="center"> <Carousel part="slide"><img src="https://images.unsplash.com/photo-1759332978018-a6efdacf4d11?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1290" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> <Carousel part="slide"><img src="https://plus.unsplash.com/premium_photo-1760541740387-e0af5182d805?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1335" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760659391924-86d657118008?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=2340" alt="Example" class="w-120 h-96 object-cover rounded-lg" /></Carousel> <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760479099629-fececb1db0cb?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1287" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760386366148-4876115c0c34?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=2340" alt="Example" class="w-120 h-96 object-cover rounded-lg" /></Carousel></Carousel>Installation
Section titled “Installation” bunx bejamas add carousel npx bejamas add carousel pnpm dlx bejamas add carousel yarn dlx bejamas add carousel---/** * @component Carousel * @title Carousel * @description A CSS scroll-snap carousel using only Tailwind classes (no JS). * * @preview * * <!-- Horizontal images with center snapping using parts --> * <Carousel class="gap-4 p-2" align="center"> * <Carousel part="slide"><img src="https://images.unsplash.com/photo-1759332978018-a6efdacf4d11?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1290" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> * <Carousel part="slide"><img src="https://plus.unsplash.com/premium_photo-1760541740387-e0af5182d805?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1335" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> * <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760659391924-86d657118008?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=2340" alt="Example" class="w-120 h-96 object-cover rounded-lg" /></Carousel> * <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760479099629-fececb1db0cb?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=1287" alt="Example" class="w-64 h-96 object-cover rounded-lg" /></Carousel> * <Carousel part="slide"><img src="https://images.unsplash.com/photo-1760386366148-4876115c0c34?ixlib=rb-4.1.0&auto=format&fit=crop&q=80&w=2340" alt="Example" class="w-120 h-96 object-cover rounded-lg" /></Carousel> * </Carousel> * * @usage * * ```astro * --- * import Carousel from '@/components/ui/Carousel.astro'; * --- * * <Carousel> * <!-- Slides rendered via part="slide"; give inner content widths (e.g., w-64) --> * <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 1</div></Carousel> * <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 2</div></Carousel> * <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 3</div></Carousel> * </Carousel> * ``` * * @props * - axis?: "x" | "y" — Scroll direction. Default: "x". * - snapType?: "mandatory" | "proximity" — Scroll-snap behavior. Default: "mandatory". * - align?: "start" | "center" | "end" — Child snap alignment. Default: "start". * - fade?: boolean — Add edge fade via CSS mask on the scroll axis. Default: false. * You can customize the fade via Tailwind mask utilities on the class prop. * - class?: string — Additional classes for the container. * * @examples * * ### Vertical list, proximity snapping * * <Carousel axis="y" align="center" snapType="proximity" class="h-64 gap-3 p-3"> * <Card class="w-full shrink-0"> <Card part="content">A</Card> </Card> * <Card class="w-full shrink-0"> <Card part="content">B</Card> </Card> * <Card class="w-full shrink-0"> <Card part="content">C</Card> </Card> * <Card class="w-full shrink-0"> <Card part="content">D</Card> </Card> * <Card class="w-full shrink-0"> <Card part="content">E</Card> </Card> * </Carousel> * * ### With fade * * <Carousel fade fadeSize="5%" class="gap-6 p-4"> * <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 1</div> * <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 2</div> * <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 3</div> * <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 4</div> * </Carousel> */
import type { HTMLAttributes, HTMLTag } from "astro/types";import { cva } from "class-variance-authority";import { cn } from "@bejamas/ui/lib/utils";
type CarouselPart = "root" | "slide";
type Axis = "x" | "y";type SnapType = "mandatory" | "proximity";type Align = "start" | "center" | "end";
type RootProps = { part?: "root"; as?: HTMLTag; axis?: Axis; snapType?: SnapType; align?: Align; fade?: boolean; class?: string;} & HTMLAttributes<"div">;
type SlideProps = { part: "slide"; as?: HTMLTag; class?: string;} & HTMLAttributes<"div">;
type Props = RootProps | SlideProps;
const { part: rawPart, as: rawTag, axis = "x" as Axis, snapType = "mandatory" as SnapType, align = "start" as Align, fade = false as boolean, class: className = "", ...rest} = Astro.props as Props;
const part: CarouselPart = (rawPart ?? "root") as CarouselPart;
const carouselVariants = cva( // Base container: overflow scroll, hide scrollbar, smooth scroll on supported browsers "relative w-full overflow-auto scroll-smooth [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden", { variants: { axis: { x: "flex flex-row snap-x", y: "flex flex-col snap-y", }, snapType: { mandatory: "snap-mandatory", proximity: "snap-proximity", }, align: { start: "[&>[data-slot=carousel-slide]]:snap-start [&>[data-slot=carousel-slide]]:shrink-0", center: "[&>[data-slot=carousel-slide]]:snap-center [&>[data-slot=carousel-slide]]:shrink-0", end: "[&>[data-slot=carousel-slide]]:snap-end [&>[data-slot=carousel-slide]]:shrink-0", }, }, defaultVariants: { axis: "x", snapType: "mandatory", align: "start", }, },);
// Default fade utilities; users can override by passing their own mask classes via `class`.const defaultFadeX = "mask-x-from-80% mask-x-to-95%";const defaultFadeY = "mask-y-from-80% mask-y-to-95%";---
{ part === "root" && (() => { const Tag = (rawTag as HTMLTag) ?? "div"; return ( <Tag {...rest} data-slot="carousel" class={cn( carouselVariants({ axis, snapType, align }), fade && axis === "x" && defaultFadeX, fade && axis === "y" && defaultFadeY, className, )} > <slot /> </Tag> ); })()}
{ part === "slide" && (() => { const Tag = (rawTag as HTMLTag) ?? "div"; return ( <Tag {...rest} data-slot="carousel-slide" class={cn("shrink-0", className)} > <slot /> </Tag> ); })()}---import Carousel from '@/components/ui/Carousel.astro';---
<Carousel> <!-- Slides rendered via part="slide"; give inner content widths (e.g., w-64) --> <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 1</div></Carousel> <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 2</div></Carousel> <Carousel part="slide"><div class="w-72 bg-muted rounded-xl p-6">Item 3</div></Carousel></Carousel>Examples
Section titled “Examples”Vertical list, proximity snapping
Section titled “Vertical list, proximity snapping”---import Card from '@bejamas/ui/components/Card.astro';import Carousel from '@bejamas/ui/components/Carousel.astro';---
<Carousel axis="y" align="center" snapType="proximity" class="h-64 gap-3 p-3"> <Card class="w-full shrink-0"> <Card part="content">A</Card> </Card> <Card class="w-full shrink-0"> <Card part="content">B</Card> </Card> <Card class="w-full shrink-0"> <Card part="content">C</Card> </Card> <Card class="w-full shrink-0"> <Card part="content">D</Card> </Card> <Card class="w-full shrink-0"> <Card part="content">E</Card> </Card></Carousel>With fade
Section titled “With fade”---import Carousel from '@bejamas/ui/components/Carousel.astro';---
<Carousel fade fadeSize="5%" class="gap-6 p-4"> <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 1</div> <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 2</div> <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 3</div> <div class="w-72 shrink-0 bg-muted rounded-xl p-6">Item 4</div></Carousel>