Pular para o conteúdo
UI

Carousel

Um carousel componível para listas horizontais ou verticais de itens, construído sobre o Embla Carousel. Aceita itens de largura fixa ou fracionária, passos configuráveis, arrastar e swipe, loop, autoplay e controles acessíveis, estilizado só com tokens do design system.

Uso

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@medway-ui/core";
 
<Carousel aria-label="Grupos para descobrir">
  <CarouselPrevious />
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselNext />
</Carousel>

Quando controls é "side", "floating" ou "outside", CarouselPrevious, CarouselContent e CarouselNext dividem uma única linha, com as setas centralizadas verticalmente na trilha, e todo outro filho (como CarouselDots) ocupa uma linha inteira só para ele, na ordem do documento. Na orientação vertical, os filhos se empilham na ordem do documento, então coloque CarouselPrevious antes de CarouselContent e CarouselNext depois.

Exemplos

Itens de largura fixa, um item por passo

Cards com largura fixa, gap de 12px e setas ao lado da trilha. As setas só aparecem a partir do breakpoint md, já que no mobile a lista é navegada por swipe. Nas bordas as setas ficam desabilitadas, e a trilha nunca muda de largura. Como as setas ficam ocultas em telas pequenas, o CarouselDots é renderizado abaixo de md (ou use outro controle que funcione com um único toque) para que a lista possa ser navegada sem arrastar (WCAG 2.5.7).

Clube de Cardiologia
Residentes de Cirurgia
Estudos de Pediatria
Corujões da madrugada
Casos Clínicos
Turma do Revalida
Foco no ENARE
Estudo no fim de semana

Passo por página

Com step="page", cada clique avança quantos itens couberem na trilha. O último passo sempre encosta o último item no fim da trilha, sem espaço vazio. Quando não há mais itens numa direção, a seta dessa direção fica desabilitada e esmaecida; use edgeBehavior="hide" para ocultá-la. A trilha também ganha um fade nas bordas que têm mais conteúdo.

Módulo 1
Módulo 2
Módulo 3
Módulo 4
Módulo 5
Módulo 6
Módulo 7
Módulo 8
Módulo 9
Módulo 10
Módulo 11
Módulo 12

Passo de N itens

Passe um número para step para avançar exatamente essa quantidade de itens por clique. Não use um valor maior que a quantidade de itens que cabem na trilha: um passo maior deixa alguns itens que nunca chegam ao início da trilha. Use "page" para avançar quantos itens couberem. Aqui cada clique avança 3 itens, e cada dot representa um passo. Como em qualquer tamanho de passo, o último passo encosta o último item no fim da trilha.

Módulo 1
Módulo 2
Módulo 3
Módulo 4
Módulo 5
Módulo 6
Módulo 7
Módulo 8
Módulo 9
Módulo 10
Módulo 11
Módulo 12

O autoplay pausa enquanto o ponteiro está sobre o carousel e enquanto a aba do navegador está em segundo plano, e retoma depois. Quando um elemento dentro do carousel recebe foco pelo teclado, a rotação para e continua parada até o usuário retomá-la com o CarouselAutoplayToggle, como recomenda o padrão de carousel do WAI-ARIA. Depois que o usuário retoma, o foco pelo teclado não a interrompe mais. O toggle permite pausar e retomar a rotação a qualquer momento (WCAG 2.2.2); coloque-o antes do conteúdo para que seja o primeiro elemento na ordem de tabulação. Navegar pelas setas, pelo teclado ou pelos dots reinicia o timer do autoplay. Clicar no dot atual realinha o slide (útil depois de um arraste livre) sem reiniciar o timer. Com prefers-reduced-motion, o autoplay começa pausado e a navegação é instantânea, inclusive a rotação que o usuário inicia pelo toggle. Por padrão, o toggle fica acima dos slides, então dá para posicioná-lo sobre eles com containerClassName (por exemplo, "absolute right-3 top-3").

Novo plano de mentoria disponível
Aula ao vivo nesta quinta
Simulado abre na semana que vem

Transição fade

Use transition="fade" para trocar os slides com fade cruzado em vez de deslizá-los. Arrastar e swipe continuam funcionando.

Novo plano de mentoria disponível
Aula ao vivo nesta quinta
Simulado abre na semana que vem

Itens por breakpoint

O tamanho do item vem do CSS, então o layout já sai correto na renderização no servidor. Aqui aparece um item no mobile, dois no tablet e três no desktop, com setas flutuando sobre a trilha.

Clube de Cardiologia
Residentes de Cirurgia
Estudos de Pediatria
Corujões da madrugada
Casos Clínicos
Turma do Revalida

Vertical

Com orientation="vertical", as setas vão para cima e para baixo da trilha e o teclado usa ↑ e ↓. Defina uma altura para o CarouselContent.

Módulo 1
Módulo 2
Módulo 3
Módulo 4
Módulo 5
Módulo 6

Controlado com setApi

O setApi dá acesso à API do Embla: scrollPrev, scrollNext, scrollTo, canScrollPrev, canScrollNext, selectedScrollSnap, scrollSnapList e eventos como select e reInit. Para uma posição controlada, use index com onIndexChange. Para controles personalizados renderizados dentro do Carousel, prefira useCarousel(): o scrollTo dele respeita prefers-reduced-motion e reinicia o timer do autoplay quando a posição muda, o que chamadas diretas ao Embla não fazem.

Módulo 1
Módulo 2
Módulo 3
Módulo 4
Módulo 5
Posição 1 de 0

Personalização

Os itens não têm estilo visual, só layout (tamanho e gap), então você pode colocar qualquer conteúdo dentro do CarouselItem. Cada parte aceita classes para os próprios elementos:

PartePropriedadeElemento
CarouselclassNameRaiz
CarouselContentviewportClassName / className / fadeClassNameViewport / trilha / os dois fades das bordas
CarouselItemclassNameItem
CarouselPrevious / CarouselNextcontainerClassName / className, além de size e variantWrapper / botão
CarouselDotsclassName / dotClassName / indicatorClassNameGrupo / área clicável de cada dot / marca visível
CarouselAutoplayTogglecontainerClassName / className, além de size e variantWrapper / botão

Em indicatorClassName, classes simples estilizam os dots inativos e classes data-[active=true]: estilizam o ativo. A linha de dots reserva 24px de altura antes de o carousel ficar pronto; se você aumentar os dots, reserve a altura deles com className (ex.: min-h-8) para o layout não deslocar. Em controls="floating", as setas outlined padrão ganham um fundo de superfície opaco; as outras variantes mantêm o próprio fundo.

Clube de Cardiologia
Residentes de Cirurgia
Estudos de Pediatria
Corujões da madrugada
Casos Clínicos
Turma do Revalida
Foco no ENARE
Estudo no fim de semana

Acessibilidade

  • A raiz tem role="region" com aria-roledescription="carousel", e o aria-label é obrigatório.
  • Cada item tem role="group", aria-roledescription="slide" e um label no formato "N de total" (dá para sobrescrever com aria-label).
  • ← e → (↑ e ↓ na vertical) movem o carousel enquanto o foco está dentro dele, exceto quando o foco está num elemento que usa essas teclas: campos de texto, selects, players de vídeo e áudio, widgets compostos (sliders, listboxes, tabs, menus, grids) e áreas que rolam no mesmo eixo do carousel, inclusive dentro de shadow DOM.
  • Quando um clique ou uma tecla de seta na seta focada a leva até uma borda, o foco vai para a seta oposta sem rolar a página. Autoplay, arraste e mudanças de index nunca movem o foco, exceto com transition="fade" (veja abaixo).
  • Teclas de seta pressionadas com Alt, Ctrl, Meta ou Shift, e teclas de seta pressionadas dentro de conteúdo renderizado em portal (popovers, dialogs), ficam por conta do navegador e desse conteúdo.
  • Uma live region visualmente oculta anuncia a posição após cada navegação: "Slide N de total", ou "Página N de total" quando cada posição agrupa vários itens. Nada é anunciado no carregamento, quando itens são adicionados ou removidos, nem quando uma mudança de opção faz o carousel restaurar a posição, e ela fica em silêncio enquanto o autoplay está girando.
  • O grupo de dots tem o label "Escolher slide", ou "Escolher página" quando cada posição agrupa vários itens, e o dot ativo tem aria-current="true".
  • Com transition="fade", os slides fora da posição atual ficam inert e ocultos para leitores de tela. Se o foco estava dentro de um slide que some, ele vai para o slide que aparece (o primeiro que não está inert), seja qual for a causa da mudança, inclusive o autoplay; quando o usuário está navegando pelo teclado, o slide focado mostra o anel de foco. Um inert que você define num item é respeitado, inclusive quando muda depois.
  • Itens envolvidos num Fragment são numerados como se fossem filhos diretos do CarouselContent.
  • Tab percorre os itens e a trilha rola até o item focado.
  • Arrastar nunca dispara o clique de botões e links dentro dos itens.
  • Setas ocultas por edgeBehavior="hide" ou hideControlsWhenFits usam visibility: hidden, então saem da ordem de tabulação sem mudar o layout. Abaixo do breakpoint de controlsFrom, elas saem do layout (display: none), e a trilha ocupa a largura das setas.

API

ComponenteDescriçãoUso
CarouselComponente raiz, dono da instância do Embla e do estado do carouselContém todos os outros componentes do carousel
CarouselContentViewport e trilha que contêm os itensEnvolve a lista de CarouselItem
CarouselItemUm slideFilho direto de CarouselContent; defina o tamanho com classes basis-*
CarouselPrevious / CarouselNextBotões de seta construídos sobre o ButtonOpcional; posicionado pela prop controls do Carousel
CarouselDotsUm dot por posição de rolagemOpcional
CarouselAutoplayTogglePausa e retoma o autoplayOpcional; só renderiza quando o autoplay está ativo
PropriedadeTipoValores possíveisPadrãoDescrição
aria-labelstringQualquer string-Obrigatório. Nome acessível da região do carousel. Não inclua a palavra "carousel".
orientationCarouselOrientation"horizontal", "vertical""horizontal"Eixo de rolagem.
stepnumber | "page"1, 2, 3..., "page"1Quantos itens cada passo avança. "page" avança quantos itens couberem na trilha. Valores abaixo de 1 ou não finitos viram 1, e frações são truncadas.
alignCarouselAlign"start", "center", "end""start"Alinhamento dos itens dentro da trilha.
loopbooleantrue, falsefalseVolta para o primeiro item depois do último.
dragFreebooleantrue, falsefalseRolagem livre com inércia em vez de encaixar num item.
transitionCarouselTransition"slide", "fade""slide"Como os itens trocam. "fade" é pensado para um item por vez.
autoplayboolean | { delay?: number }true, false, { delay: 5000 }falseGira automaticamente. delay é em milissegundos (padrão 5000); um delay que não seja número positivo vira 5000.
controlsCarouselControls"side", "floating", "outside", "inline""side"Onde as setas ficam: ao lado da trilha, flutuando sobre ela, fora dos limites do carousel ou onde você as colocar ("inline"). "outside" precisa de espaço livre dos dois lados do carousel; em layouts de largura total, combine com controlsFrom.
controlsFromCarouselControlsFrom"always", "md", "lg""always"Breakpoint a partir do qual as setas aparecem. Abaixo dele, renderize o CarouselDots para a lista não depender de arraste.
edgeBehaviorCarouselEdgeBehavior"disable", "hide""disable"O que a seta faz quando não há mais nada na direção dela: "disable" a mantém visível, desabilitada e esmaecida; "hide" a oculta. Nenhuma das opções muda o layout.
hideControlsWhenFitsbooleantrue, falsetrueOculta setas, dots e o toggle de autoplay quando todos os itens cabem na trilha.
indexnumber0, 1, 2...-Posição de rolagem controlada (índice do scroll snap). Se o pai renderizar de novo sem atualizar index depois do onIndexChange, o carousel volta para index. Um pai que atualiza index mais tarde (por exemplo, depois de uma requisição) não é desfeito nesse meio-tempo, desde que não renderize de novo com o index antigo antes disso. O carousel reconhece a renderização do pai pelas props, então um pai que renderiza de novo com exatamente as mesmas props (children e callbacks memoizados) não é percebido e o carousel mantém a posição.
defaultIndexnumber0, 1, 2...0Posição de rolagem inicial no modo não controlado.
onIndexChange(index: number) => void--Função chamada quando a posição de rolagem selecionada muda por interação do usuário, autoplay ou remoção de itens. Não é chamada quando a mudança vem de index.
setApi(api: CarouselApi) => void--Recebe a API do Embla quando o carousel fica pronto.
optsCarouselOptionsOpções do Embla-Opções extras do Embla (ex.: duration, breakpoints). As props dedicadas acima têm prioridade sobre as opções base, mas as opções dentro de breakpoints as sobrescrevem enquanto a media query correspondente estiver ativa.
classNamestringQualquer classe CSS-Classes CSS adicionais para a raiz.

CarouselContent

PropriedadeTipoValores possíveisPadrãoDescrição
gapCarouselGap"none", "xs", "sm", "md", "lg", "xl""md"Espaço entre os itens: 0, 8, 12, 16, 24 ou 32px.
fadeEdgesbooleantrue, falsefalseAplica fade nas bordas que ainda têm conteúdo para rolar, como nas Tabs.
fadeClassNamestringQualquer classe CSS-Classes CSS adicionais para os dois fades das bordas, ex.: from-surface-card quando o carousel fica sobre um card.
viewportClassNamestringQualquer classe CSS-Classes CSS adicionais para o viewport (o elemento que recorta a trilha).
classNamestringQualquer classe CSS-Classes CSS adicionais para a trilha.

CarouselItem

Aceita todas as props de div. Defina o tamanho com classes como basis-full, basis-1/3, md:basis-1/2 ou uma largura fixa.

CarouselPrevious / CarouselNext

Aceitam todas as props de Button, exceto iconLeft, iconRight e children. Use size ("sm", "md", "lg" ou "xl", os mesmos tamanhos do Button) para mudar o tamanho da seta; as setas mantêm a posição em todo modo de controls e em toda orientação. Use className para estilizar o botão e containerClassName para estilizar o elemento que o envolve. A variant padrão é "outlined", o size padrão é "md" e o aria-label padrão é "Anterior" / "Próximo". Um onClick que chama event.preventDefault() cancela a navegação. disabled se soma ao estado de borda: pode desabilitar uma seta, mas nunca habilita uma seta numa borda. Nos modos "floating" e "outside", uma seta desabilitada continua bloqueando cliques, então eles nunca chegam ao slide embaixo dela.

<CarouselPrevious size="lg" variant="regular-soft" className="shadow-level-1" />

CarouselDots

PropriedadeTipoValores possíveisPadrãoDescrição
getDotLabel(index: number, total: number) => string-"Ir para o slide N" / "Ir para a página N"Label acessível de cada dot. Por padrão, os dots falam em páginas quando cada posição agrupa vários itens (um step maior que 1, ou "page" com mais de um item por página). Caso contrário, cada dot recebe o nome do primeiro slide que a posição dele mostra.
dotClassNamestringQualquer classe CSS-Classes CSS adicionais para a área clicável de cada dot (24px por padrão).
indicatorClassNamestringQualquer classe CSS-Classes CSS adicionais para a marca visível dentro de cada dot. Classes simples estilizam os dots inativos; use o prefixo data-[active=true]: para estilizar o ativo.
classNamestringQualquer classe CSS-Classes CSS adicionais para o grupo de dots.

CarouselAutoplayToggle

Aceita todas as props de Button, exceto iconLeft, iconRight e children. A variant padrão é "outlined" e o size padrão é "sm". O label alterna entre "Pausar rotação automática" e "Iniciar rotação automática".

useCarousel

Hook que retorna o estado do carousel (selectedIndex, snapCount, canScrollPrev, canScrollNext, isScrollable, autoplay) e as ações (scrollPrev, scrollNext, scrollTo) para montar controles personalizados dentro do Carousel. O tipo de retorno é CarouselState.