Carousel is the kit's thin wrapper around @arco-design/mobile-react's <Carousel>
with three defaults baked in for the common mini-app onboarding / image-swipe pattern:
autoPlay={false}— mobile UX never advances without user input.loop={false}— predictable swipe-to-end behaviour.indicatorType="circle"+indicatorOutside— circle dots rendered below the slide area, not on top of content.
Every default is overridable by passing the prop explicitly — see the QR-card pattern below for the typical "I want my own counter and arrows" case.
Default
The standard onboarding-drawer pattern: three slides, swipe, dot indicators outside.
import { Body1, Carousel, Flex, Title1 } from "@appboxo/ui-kit"
import { PreviewLayout, Section } from "./_section"
const SLIDES = [
{
bg: "linear-gradient(135deg, #f4f9f5 0%, #dcf2e3 100%)",
title: "Skip the Line",
body: "Pre-book Fast Track security access and breeze through airport checkpoints.",
},
{
bg: "linear-gradient(135deg, #ebf4ff 0%, #d1e7ff 100%)",
title: "Book in a Few Taps",
body: "Choose your airport, terminal, and time — we'll check availability instantly.",
},
{
bg: "linear-gradient(135deg, #fff7e0 0%, #ffeec2 100%)",
title: "Stress-Free Entry",
body: "Show your QR code at the Fast Track lane and pass security with ease.",
},
] as const
const Slide = ({
bg,
title,
body,
}: {
bg: string
title: string
body: string
}) => (
<div
style={{
height: 280,
background: bg,
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 24,
borderRadius: 16,
}}
>
<Flex gap={8} align="center">
<Title1 weight="bold" style={{ textAlign: "center" }}>
{title}
</Title1>
<Body1 style={{ textAlign: "center", maxWidth: 280 }}>{body}</Body1>
</Flex>
</div>
)
export function CarouselDefaultPreview() {
return (
<PreviewLayout>
<Section
title="Default"
description="Bakes in no auto-play, no loop, circle indicator outside the slide area — the four flags every onboarding carousel sets."
>
<Carousel>
{SLIDES.map((s) => (
<Slide key={s.title} {...s} />
))}
</Carousel>
</Section>
</PreviewLayout>
)
}
Custom controls (no indicator)
showIndicator={false} removes the built-in dots so the app can render its own counter
/ arrow chrome. ref={carouselRef} exposes Arco's CarouselRef (also re-exported from
the kit) for imperative .changeIndex(idx) calls.
import { useRef, useState } from "react"
import { Body1, Carousel, Flex, type CarouselRef } from "@appboxo/ui-kit"
import { PreviewLayout, Section } from "./_section"
const SLIDES = [
{ bg: "linear-gradient(135deg, #f4f9f5 0%, #dcf2e3 100%)", title: "Slide 1" },
{ bg: "linear-gradient(135deg, #ebf4ff 0%, #d1e7ff 100%)", title: "Slide 2" },
{ bg: "linear-gradient(135deg, #fff7e0 0%, #ffeec2 100%)", title: "Slide 3" },
] as const
export function CarouselNoIndicatorPreview() {
const carouselRef = useRef<CarouselRef>(null)
const [index, setIndex] = useState(0)
return (
<PreviewLayout>
<Section
title="Custom controls"
description="Pass `showIndicator={false}` to suppress the built-in dots; drive the carousel from any external chrome via a ref. Same pattern pass-freedom /passes/detail uses for its QR-card row."
>
<Carousel
ref={carouselRef}
showIndicator={false}
onChange={setIndex}
>
{SLIDES.map((s) => (
<div
key={s.title}
style={{
height: 220,
background: s.bg,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 16,
fontSize: 28,
fontWeight: 700,
}}
>
{s.title}
</div>
))}
</Carousel>
<Flex vertical={false} gap={12} justify="center" align="center">
<button
onClick={() => carouselRef.current?.changeIndex(Math.max(0, index - 1))}
disabled={index === 0}
style={{
padding: "8px 16px",
borderRadius: 999,
background: "var(--fill-2)",
border: 0,
cursor: index === 0 ? "not-allowed" : "pointer",
opacity: index === 0 ? 0.5 : 1,
}}
>
Prev
</button>
<Body1 weight="semibold" color="text-4">
{index + 1} / {SLIDES.length}
</Body1>
<button
onClick={() =>
carouselRef.current?.changeIndex(
Math.min(SLIDES.length - 1, index + 1),
)
}
disabled={index === SLIDES.length - 1}
style={{
padding: "8px 16px",
borderRadius: 999,
background: "var(--fill-2)",
border: 0,
cursor:
index === SLIDES.length - 1 ? "not-allowed" : "pointer",
opacity: index === SLIDES.length - 1 ? 0.5 : 1,
}}
>
Next
</button>
</Flex>
</Section>
</PreviewLayout>
)
}
API
The component accepts every Arco CarouselProps prop verbatim — the kit just changes
four of the defaults. The typings are re-exported as:
import { Carousel, type CarouselProps, type CarouselRef } from "@appboxo/ui-kit"Installation
pnpm add @appboxo/ui-kit