Carrosséis CSS modernos: sem JavaScript
Descubra como criar carrosséis CSS funcionais sem JavaScript, melhorando a acessibilidade e desempenho.

Historicamente, criar sliders ou carrosséis significava lidar com JavaScript, problemas de acessibilidade e tentativas de minimizar mudanças de layout. Bibliotecas externas ajudavam, mas prejudicavam o desempenho, customização e tempo de carregamento. Mesmo com plugins corretos, ainda era preciso gerenciar estados de foco, suporte a leitores de tela e comportamento de encaixe manualmente.
Este artigo explora os novos recursos do CSS que permitem construir carrosséis totalmente funcionais sem JavaScript e as melhorias de acessibilidade dessa abordagem.
Novidades no CSS para carrosséis
O CSS já possuía recursos para criar áreas roláveis usando overflow
e snapping com scroll-snap-type
e scroll-snap-align
. Porém, essas funções não ofereciam uma maneira de os usuários navegarem ativamente pelo scroller.
O Chrome 135 introduz dois novos pseudo-elementos: ::scroll-button
e ::scroll-marker()
, além de uma nova pseudo-classe, :target-current
, que se aplica especificamente a elementos de marcadores de rolagem.
O pseudo-elemento ::scroll-button()
::scroll-button()
permite a criação de botões de rolagem interativos como pseudo-elementos. Eles são gerados dentro de um contêiner de rolagem somente quando seu valor de content
é diferente de none
. Eles aparecem como elementos posicionados como irmãos dos nós filhos e podem ser estilizados e posicionados livremente.
Esses botões de rolagem permitem que os usuários se movam pela área rolável em uma determinada direção — esquerda, direita, cima ou baixo. Por padrão, cada clique rola cerca de 85% da área visível. Você pode alterar isso usando snapping de rolagem e alvos de encaixe definidos. Um contêiner de rolagem pode ter até quatro botões de rolagem distintos, cada um vinculado a uma direção específica.
Eis a sintaxe básica:
::scroll-button() {}
O argumento pode ser:
*
: Alvo para todos os botões de rolagem de elementosup
,down
,left
,right
: Direções físicasblock-start
,block-end
,inline-start
,inline-end
: Direções lógicas
O pseudo-elemento só se torna visível se você definir explicitamente seu content
. Aqui está como adicionar um botão de rolagem que rola para a esquerda:
div::scroll-button(left) { content: "Button"; }
Se você quiser mostrar botões de rolagem para todas as direções:
div::scroll-button(*) { content: "Button"; }
Isso criará quatro botões, cada um para uma direção diferente:
Para estilização, você pode usar as pseudo-classes enabled/disabled usuais. Quando você chega ao final da área de rolagem em qualquer direção, o botão é automaticamente desativado. E assim como os botões regulares, você pode estilizar os botões de rolagem como quiser — cores, efeitos de hover, etc.
O pseudo-elemento ::scroll-marker()
O pseudo-elemento ::scroll-marker
representa um marcador visual vinculado a um item rolável/dentro de um contêiner de rolagem. Você pode clicar em um marcador para navegar até o item correspondente ou simplesmente visualizar o progresso da rolagem, como uma barra de progresso. Todos os marcadores são agrupados em um contêiner representado pelo pseudo-elemento ::scroll-marker-group
.
Os marcadores de rolagem só são gerados quando sua propriedade content
é definida para um valor diferente de none
, semelhante aos botões de rolagem. O número de marcadores corresponde ao número de filhos no contêiner de rolagem. O grupo aparece antes ou depois do conteúdo do contêiner de rolagem, com base na propriedade scroll-marker-group
.
A sintaxe é a seguinte:
::scroll-marker { content: ; }
O pode ser qualquer coisa, como texto, contadores ou ícones, desde que não seja
none
. Isso oferece muita flexibilidade para construir coisas como carrosséis com guias, miniaturas ou o que se encaixar no seu design. Por exemplo, você pode combinar content: ""
com uma imagem de fundo para criar um marcador de miniatura para o seu carrossel.
Aqui está um exemplo básico com um contêiner de rolagem e alguns marcadores:
div {
display: flex;
gap: 10px;
padding: 10px;
overflow-x: auto;
scroll-marker-group: after;
}
.slide {
background-color: #e2e2e2;
height: 250px;
min-width: 90%;
padding-top: 20px;
}
.slide::scroll-marker {
content: "";
border: 1px solid red;
height: 1em;
width: 1em;
}
div::scroll-marker-group {
display: flex;
gap: 10px;
margin-left: 10px;
}
Isso ficaria assim:
É uma interface muito básica, mas funciona. Você pode melhorá-la bastante adicionando snapping de rolagem e habilitando rolagem suave, mas discutiremos isso mais adiante. O único inconveniente real agora? Não há como saber qual marcador está ativo no momento. É isso que veremos a seguir.
O pseudo-classe :target-current
A pseudo-classe :target-current
permite estilizar o marcador de rolagem ativo. Este é o ::scroll-marker
que está atualmente alinhado com a posição de rolagem dentro de um scroll-marker-group
. Funciona apenas em ::scroll-marker
, e é ótimo para mostrar qual item está atualmente em exibição.
Neste caso, esta linha resolve o problema:
::scroll-marker:target-current {
background-color: red;
}
Nosso exemplo agora ficaria assim:
Construindo um carrossel apenas com CSS
Agora que discutimos os novos recursos, vamos combiná-los para criar um carrossel completo. Vamos focar mais na estilização e na experiência do usuário, já que a funcionalidade básica de botão e marcador já foi coberta.
Configurando o contêiner de rolagem
Começaremos criando um contêiner rolável com overflow horizontal. Usaremos scroll-snap-type
para garantir que os slides se encaixem no lugar ao navegar e ocultaremos a barra de rolagem para tornar a interface mais limpa:
.carousel {
width: 80%;
display: flex;
gap: 10px;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-marker-group: after;
position: relative;
}
.carousel::-webkit-scrollbar {
display: none;
scrollbar-width: none;
}
Você também pode habilitar a rolagem suave no contêiner, embora seja importante levar em consideração as preferências do usuário aqui:
@media (prefers-reduced-motion: no-preference) {
.carousel {
scroll-behavior: smooth;
}
}
Em seguida, vamos definir alguns elementos básicos de slide. Usar scroll-snap-align: center
ajuda a manter cada slide centralizado quando é rolado para a visão.
N.B., se você quiser que os botões de rolagem rolem um único slide por vez, adicione
scroll-snap-stop: always
no slide junto comscroll-snap-type: x mandatory
(que já está aplicado em nosso exemplo):
.slide {
scroll-snap-align: center;
height: 250px;
min-width: 400px;
}
Adicionando botões de rolagem
Agora, vamos adicionar botões de rolagem para navegar no carrossel. Usaremos ícones para o content
desta vez e colocaremos os botões nas bordas horizontais. Um recurso útil do CSS que ajuda com coisas assim é CSS anchor positioning. No entanto, ainda é muito novo e tem pouca compatibilidade com navegadores, então vamos nos ater ao posicionamento absoluto e relativo para este guia:
.carousel::scroll-button(left) {
content: "<" / "Prev";
left: 5%;
}
.carousel::scroll-button(right) {
content: "˃" / "Next";
right: 5%;
}
.carousel::scroll-button(*){
position: absolute;
top: 160px;
cursor: pointer;
}
Também podemos personalizar estados de botão como hover e disabled. A pseudo-classe :disabled
desativa automaticamente o botão quando você está no início ou no final do contêiner de rolagem:
.carousel::scroll-button(*):hover{
background-color: #e1e1e1;
}
.carousel::scroll-button(*):disabled{
opacity: 0.2;
color: #000;
}
Usando marcadores de rolagem
Por fim, vamos adicionar alguns marcadores, habilitando o grupo de marcadores e estilizando os marcadores individuais. Usaremos posicionamento absoluto para centralizar o grupo de marcadores, mas, mais uma vez, o posicionamento de âncora pode ser algo que você também possa se interessar:
.carousel::scroll-marker-group {
display: flex;
gap: 10px;
position: absolute;
left: 50%;
margin-top: 25px;
transform: translateX(-50%);
}
.slide::scroll-marker {
content: "";
border: 1px solid #424242;
border-radius: 50%;
height: 20px;
width: 20px;
transition: 0.1s all ease-in-out;
}
Podemos então estilizar o marcador ativo, bem como estados de marcador como hover
:
.slide::scroll-marker:hover {
background-color: #e3e3e3;
}
.slide::scroll-marker:target-current {
background-color: #424242;
}
Aqui está um exemplo funcional de tudo junto:
Veja o Pen
CSS Only Carousel por Saleh-Mubashar (@saleh-mubashar)
on CodePen.
Acessibilidade
O ótimo desses recursos é que eles não apenas reduzem a necessidade de JavaScript, mas também vêm com acessibilidade embutida.
Botões de rolagem
Os botões de rolagem funcionam como elementos regulares, exceto que são renderizados como pseudo-elementos. Isso significa que são focáveis, e você não precisa adicionar manualmente nomes acessíveis, pois são gerados automaticamente.
Por exemplo, o código a seguir será anunciado pelos leitores de tela como “Rolar para a direita”, independentemente do que o conteúdo visual seja:
.scroll-container::scroll-button(right) {
content: "Button"; /* Anuncia "Rolar para a direita" */
}
No entanto, se você quiser que os leitores de tela anunciem algo específico, precisará incluí-lo como texto alternativo, mesmo que seja o mesmo que o rótulo visual:
.scroll-container::scroll-button(right) {
content: "⮕" / "Rola para a direita"; /* Anuncia o texto que fornecemos*/
}
Marcadores de rolagem
Os marcadores de rolagem também são totalmente acessíveis. Se não for dado texto alternativo ao conteúdo, os leitores de tela anunciarão algo como “selecionado, guia, 1 de 5.” Você pode, é claro, substituir isso, assim como com botões de rolagem, fornecendo um valor de texto alternativo através da segunda parte da propriedade content
:
::scroll-marker {
content: "" / "Miniatura do slide 1";
}
O grupo de marcadores de rolagem é tratado como um único grupo focável usando semântica de tablist com um tabindex
de -1. Quando um usuário tabula para o grupo de marcadores, ele pode navegar entre os marcadores individuais usando as teclas de seta.
Compatibilidade com navegadores
Até o momento, o ::scroll-button()
e o ::scroll-marker
são suportados apenas no Chrome 135+ e Edge 135+. Isso torna o recurso um tanto experimental no momento, mas espera-se que o suporte se expanda.
Você pode verificar compatibilidades detalhadas aqui:
Além desses, todos os outros recursos discutidos nesta seção estão amplamente disponíveis.
Obrigado por ler! O CSS está lançando novos recursos incríveis o tempo todo, e ferramentas como ::scroll-button
e ::scroll-marker
mostram o quanto o estilo nativo evoluiu.