Carousel
Un carrusel sobre Embla con la API de shadcn: flechas de 28 sobre el material translúcido, puntos y «2 de 5» para el lector.
import { Carousel, CarouselContent, CarouselDots, CarouselItem, … } from "sebs7n-ui/carousel"Ejemplos
Planes
Las flechas son botones de 28 sobre el material translúcido; los puntos saltan a cada uno. ←/→ con el foco adentro.
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"
function Plans() {
return (
<Carousel aria-label="Planes" className="w-full max-w-sm">
<CarouselContent>
{PLANS.map((plan) => (
<CarouselItem key={plan.name}>
<Card>
<CardHeader>
<CardTitle>{plan.name}</CardTitle>
<CardDescription>{plan.detail}</CardDescription>
</CardHeader>
<CardContent className="text-title-1 text-label tabular-nums">{plan.price}</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
<CarouselDots />
</Carousel>
)
}Varios a la vista
`basis-1/2` en cada ítem y `opts={{ align: "start" }}`: dos por vez, de a uno.
import { Card, CardDescription, CardHeader, CardTitle } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"
function Several() {
return (
<Carousel aria-label="Planes, de a dos" className="w-full max-w-lg" opts={{ align: "start" }}>
<CarouselContent>
{PLANS.map((plan) => (
<CarouselItem className="basis-1/2" key={plan.name}>
<Card size="sm">
<CardHeader>
<CardTitle>{plan.name}</CardTitle>
<CardDescription>{plan.price}</CardDescription>
</CardHeader>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
<CarouselDots />
</Carousel>
)
}Sobre la foto
`bleed` y `controls="overlay"`: la imagen de borde a borde de la card y las flechas y los puntos encima, sobre el gris del tooltip. Con el puntero, las flechas aparecen al pasar; con el dedo, se desliza.
import { Card, CardContent } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"
function Photos() {
return (
<Card className="w-full max-w-xs gap-0 overflow-hidden py-0">
<Carousel aria-label="Factura 0012 escaneada" bleed controls="overlay" opts={{ loop: true }}>
<CarouselContent>
{PAGES.map((page) => (
<CarouselItem key={page.name}>
<div aria-label={page.name} className="aspect-4/3" role="img" style={{ background: page.background }} />
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
<CarouselDots />
</Carousel>
<CardContent className="p-4">
<p className="text-headline text-label">Factura 0012</p>
<p className="text-callout text-label-secondary">Acme S.A. · 3 páginas</p>
</CardContent>
</Card>
)
}Props
Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».
Carousel
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
aria-label | string | — | Nombre accesible del elemento. |
aria-labelledby | string | — | El id del título que nombra la región («Nuestros planes»). Le gana a labels.label. |
bleed | boolean | false | A sangre: la vista sin el aire para la sombra ni la máscara de los costados, y las diapositivas sin separación. Para fotos de borde a borde, como la de una card de producto. |
controls | "outside" | "overlay" | "outside" | overlay: las flechas y los puntos van encima de la foto, sobre el gris oscuro del tooltip con desenfoque (contraste garantizado sobre cualquier imagen); las flechas aparecen con el puntero encima o con foco, y con el dedo se desliza. Por defecto (outside), los de 2.0. |
labels | Partial<CarouselLabels> | — | Textos: label (el nombre de la región sin aria-label), carousel, slide, previous, next, of y goTo. Los que vienen por defecto son carouselLabels. |
onKeyDown | KeyboardEventHandler<T> | — | Corre antes que ←/→: con event.preventDefault() el carrusel no se mueve. Un control de adentro que haga preventDefault también lo frena. |
opts | CarouselOptions | — | Las opciones de Embla (loop, align, slidesToScroll…). |
orientation | "horizontal" | "vertical" | "horizontal" | horizontal (por defecto) o vertical. |
plugins | CarouselPlugin | — | Los plugins de Embla (autoplay, ruedita…). |
setApi | (api: CarouselApi) => void | — | Recibe la API de Embla, para manejarlo desde afuera. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
CarouselContent
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
CarouselDots
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
CarouselItem
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
CarouselNext
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
variant | "link" | "default" | "accent" | "secondary" | "plain" | "ghost" | "destructive" | "destructive-plain" | "ghost" | La variante del botón. Por defecto ghost, sobre el material translúcido. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
CarouselPrevious
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
variant | "link" | "default" | "accent" | "secondary" | "plain" | "ghost" | "destructive" | "destructive-plain" | "ghost" | La variante del botón. Por defecto ghost, sobre el material translúcido. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
Teclado
- ← / →
- Con el foco adentro, la parada anterior y la siguiente (↑/↓ si es vertical). En un control que ya usa las flechas (campo, slider, radios, listbox, combobox) son del control.
- Tab
- Recorre lo de adentro, las flechas y los puntos.
- Enter · Espacio
- En una flecha o un punto, mueven el carrusel.
Accesibilidad
- Es una región con
aria-roledescription«carrusel»: nombrala conaria-label(«Planes»); sin nombre se llama «Carrusel» (labels.label). - Cada diapositiva es un
group«diapositiva» llamado «2 de 5»; las flechas, «Diapositiva anterior» y «Diapositiva siguiente»; cada punto es una parada, «Ir a la página 2 de 3» (conslidesToScroll> 1 una página tiene varias diapositivas), y el actual llevaaria-current. - En la punta, la flecha que no lleva a ningún lado se apaga y se esconde (salvo con
loop). - Con movimiento reducido salta en vez de deslizar.
Reglas de uso
- Para fotos (la de una card de producto):
bleedsaca el aire para la sombra, la máscara de los costados y la separación entre diapositivas;controls="overlay"pone flechas y puntos encima de la imagen, en el gris opaco del tooltip con un filo blanco: 3:1 sobre una foto blanca y sobre una negra, y el foco de los puntos queda sobre la pastilla. Las flechas aparecen con el puntero encima o con foco de teclado (también en un iPad con teclado); con el dedo no se ven ni se tocan (se desliza). - Para mirar de a uno algo que se compara poco: planes, fotos de un comprobante. Si hay que comparar, es una grilla.
optsypluginsvan derecho a Embla (loop,align, autoplay);setApida su API.basis-1/2enCarouselItempara ver dos.- No pasa solo: un carrusel que rota necesita un botón de pausa, y eso es de la app con el plugin de autoplay.
- `embla-carousel-react` es un peer opcional:
npm install embla-carousel-reacten la app que lo usa. Solo por subpath (sebs7n-ui/carousel).