Una base predecible
El área de scroll encaja cada tarjeta en una posición estable.
Combina scroll snap con ::scroll-button(), ::scroll-marker y ::scroll-marker-group para explorar un carrusel nativo con CSS. Compara los modos links y tabs, su navegación, accesibilidad y fallback en la guía.
Arrastra, usa la rueda, el teclado o los controles generados por el navegador.
Tu navegador no admite el modo seleccionado. Conserva los marcadores por posición con el comportamiento propio de esta versión; el código muestra el modo solicitado.
Tu navegador todavía no renderiza los botones o marcadores experimentales. El carrusel mantiene el scroll y scroll snap como fallback.
<ul class="carousel" aria-label="Ejemplos de carrusel nativo con CSS" tabindex="0">
<li
class="slide"
tabindex="-1"
data-marker-label="Una base predecible"
style="--slide-accent: #ffb38a"
>
<article class="slide-card">
<p class="slide-eyebrow">01 · Scroll snap</p>
<h2>Una base predecible</h2>
<p>El área de scroll encaja cada tarjeta en una posición estable.</p>
</article>
</li>
<li
class="slide"
tabindex="-1"
data-marker-label="Controles desde CSS"
style="--slide-accent: #72d6c9"
>
<article class="slide-card">
<p class="slide-eyebrow">02 · Scroll buttons</p>
<h2>Controles desde CSS</h2>
<p>El navegador crea botones anterior y siguiente sin añadir markup.</p>
</article>
</li>
<li
class="slide"
tabindex="-1"
data-marker-label="Paginación nativa"
style="--slide-accent: #b6a8ff"
>
<article class="slide-card">
<p class="slide-eyebrow">03 · Scroll markers</p>
<h2>Paginación nativa</h2>
<p>Cada slide genera un marcador que navega hasta su propio destino.</p>
</article>
</li>
<li
class="slide"
tabindex="-1"
data-marker-label="Estado sin scripts"
style="--slide-accent: #f7a6c4"
>
<article class="slide-card">
<p class="slide-eyebrow">04 · Target current</p>
<h2>Estado sin scripts</h2>
<p>El marcador actual sigue al slide visible mediante :target-current.</p>
</article>
</li>
</ul><ul class="flex flex-row gap-[16px] [inline-size:min(100%,_56rem)] m-0 p-0 overflow-x-auto [overscroll-behavior-inline:contain] scroll-smooth motion-reduce:scroll-auto [scroll-snap-type:inline_mandatory] [scroll-marker-group:after] supports-[scroll-marker-group:after_links]:[scroll-marker-group:after_links] [anchor-name:--carousel] [&::scroll-marker-group]:flex [&::scroll-marker-group]:flex-row [&::scroll-marker-group]:items-center [&::scroll-marker-group]:justify-center [&::scroll-marker-group]:gap-3 [&::scroll-marker-group]:pt-5 [&::scroll-marker-group]:pb-2 [&::scroll-button(*)]:absolute [&::scroll-button(*)]:[position-anchor:--carousel] [&::scroll-button(*)]:z-[1] [&::scroll-button(*)]:grid [&::scroll-button(*)]:place-items-center [&::scroll-button(*)]:size-10 [&::scroll-button(*)]:border-0 [&::scroll-button(*)]:rounded-full [&::scroll-button(*)]:bg-[#172033] [&::scroll-button(*)]:text-white [&::scroll-button(*)]:[font:inherit] [&::scroll-button(*)]:text-[1.15rem] [&::scroll-button(*)]:shadow-[0_0.75rem_2rem_rgb(0_0_0/0.24)] [&::scroll-button(*)]:cursor-pointer [&::scroll-button(*)]:transition-[background-color,opacity,scale] [&::scroll-button(*)]:duration-200 [&::scroll-button(*):hover]:bg-[#7c5cff] [&::scroll-button(*):hover]:scale-[1.08] [&::scroll-button(*):focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_42%,_transparent)] [&::scroll-button(*):focus-visible]:outline-offset-3 [&::scroll-button(*):disabled]:opacity-[0.35] [&::scroll-button(*):disabled]:cursor-not-allowed [&::scroll-button(inline-start)]:content-['←'] [&::scroll-button(inline-start)]:[position-area:left_center] [&::scroll-button(inline-start)]:[translate:60%] [&::scroll-button(inline-end)]:content-['→'] [&::scroll-button(inline-end)]:[position-area:right_center] [&::scroll-button(inline-end)]:[translate:-60%]" aria-label="Ejemplos de carrusel nativo con CSS" tabindex="0">
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:[content:''_/_attr(data-marker-label)] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110" tabindex="-1" data-marker-label="Una base predecible">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#ffb38a_72%,#172033)] rounded-[1.5rem] bg-[#ffb38a] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">01 · Scroll snap</p>
<h2>Una base predecible</h2>
<p>El área de scroll encaja cada tarjeta en una posición estable.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:[content:''_/_attr(data-marker-label)] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110" tabindex="-1" data-marker-label="Controles desde CSS">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#72d6c9_72%,#172033)] rounded-[1.5rem] bg-[#72d6c9] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">02 · Scroll buttons</p>
<h2>Controles desde CSS</h2>
<p>El navegador crea botones anterior y siguiente sin añadir markup.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:[content:''_/_attr(data-marker-label)] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110" tabindex="-1" data-marker-label="Paginación nativa">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#b6a8ff_72%,#172033)] rounded-[1.5rem] bg-[#b6a8ff] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">03 · Scroll markers</p>
<h2>Paginación nativa</h2>
<p>Cada slide genera un marcador que navega hasta su propio destino.</p>
</article>
</li>
<li class="list-none flex-[0_0_84%] [scroll-snap-align:center] [scroll-snap-stop:always] [&::scroll-marker]:[content:''_/_attr(data-marker-label)] [&::scroll-marker]:[inline-size:20px] [&::scroll-marker]:[block-size:20px] [&::scroll-marker]:border-2 [&::scroll-marker]:border-[#64748b] [&::scroll-marker]:rounded-full [&::scroll-marker]:bg-white [&::scroll-marker]:cursor-pointer [&::scroll-marker]:transition-[background-color,border-color,scale] [&::scroll-marker]:duration-200 [&::scroll-marker:hover]:border-[#7c5cff] [&::scroll-marker:hover]:bg-[color-mix(in_oklch,#7c5cff_18%,#fff)] [&::scroll-marker:focus-visible]:[outline:3px_solid_color-mix(in_oklch,_#7c5cff_38%,_transparent)] [&::scroll-marker:focus-visible]:outline-offset-2 [&::scroll-marker:target-current]:border-[#172033] [&::scroll-marker:target-current]:bg-[#172033] [&::scroll-marker:target-current]:shadow-[0_0_0_2px_#fff,0_0_0_4px_#172033] [&::scroll-marker:target-current]:scale-110" tabindex="-1" data-marker-label="Estado sin scripts">
<article class="box-border grid content-end gap-2 min-block-[18rem] p-[clamp(1.5rem,5vw,3rem)] border border-[color-mix(in_oklch,#f7a6c4_72%,#172033)] rounded-[1.5rem] bg-[#f7a6c4] text-[#172033] *:m-0 [&_h2]:max-inline-[16ch] [&_h2]:text-[clamp(1.75rem,6vw,3.5rem)] [&_h2]:leading-none [&_h2]:text-balance">
<p class="text-[rgb(23_32_51/0.72)] font-bold tracking-[0.08em] uppercase">04 · Target current</p>
<h2>Estado sin scripts</h2>
<p>El marcador actual sigue al slide visible mediante :target-current.</p>
</article>
</li>
</ul>.carousel {
display: flex;
flex-direction: row;
gap: 16px;
inline-size: min(100%, 56rem);
margin: 0;
padding: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-behavior: smooth;
scroll-snap-type: inline mandatory;
scroll-marker-group: after;
scroll-marker-group: after links;
anchor-name: --carousel;
}
.carousel > .slide {
list-style: none;
flex: 0 0 84%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
.slide-card {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
min-block-size: 18rem;
padding: clamp(1.5rem, 5vw, 3rem);
border: 1px solid color-mix(in oklch, var(--slide-accent) 72%, #172033);
border-radius: 1.5rem;
background: var(--slide-accent);
color: #172033;
}
.slide-card > * {
margin: 0;
}
.slide-card h2 {
max-inline-size: 16ch;
font-size: clamp(1.75rem, 6vw, 3.5rem);
line-height: 1;
text-wrap: balance;
}
.slide-eyebrow {
color: rgb(23 32 51 / 0.72);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.carousel::scroll-marker-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding-block: 1.25rem 0.5rem;
}
.carousel > .slide::scroll-marker {
content: "" / attr(data-marker-label);
inline-size: 20px;
block-size: 20px;
border: 2px solid #64748b;
border-radius: 50%;
background: #fff;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, scale 0.2s;
}
.carousel > .slide::scroll-marker:hover {
border-color: #7c5cff;
background: color-mix(in oklch, #7c5cff 18%, #fff);
}
.carousel > .slide::scroll-marker:focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 38%, transparent);
outline-offset: 2px;
}
.carousel > .slide::scroll-marker:target-current {
border-color: #172033;
background: #172033;
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #172033;
scale: 1.1;
}
.carousel::scroll-button(*) {
position: absolute;
position-anchor: --carousel;
z-index: 1;
display: grid;
place-items: center;
inline-size: 2.5rem;
block-size: 2.5rem;
border: 0;
border-radius: 50%;
background: #172033;
color: #fff;
font: inherit;
font-size: 1.15rem;
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 0.24);
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s, scale 0.2s;
}
.carousel::scroll-button(*):hover {
background: #7c5cff;
scale: 1.08;
}
.carousel::scroll-button(*):focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 42%, transparent);
outline-offset: 3px;
}
.carousel::scroll-button(*):disabled {
opacity: 0.35;
cursor: not-allowed;
}
.carousel::scroll-button(inline-start) {
content: "←";
position-area: left center;
translate: 60%;
}
.carousel::scroll-button(inline-end) {
content: "→";
position-area: right center;
translate: -60%;
}
@media (prefers-reduced-motion: reduce) {
.carousel {
scroll-behavior: auto;
}
}.carousel {
display: flex;
flex-direction: row;
gap: 16px;
inline-size: min(100%, 56rem);
margin: 0;
padding: 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-behavior: smooth;
scroll-snap-type: inline mandatory;
scroll-marker-group: after;
scroll-marker-group: after links;
anchor-name: --carousel;
> .slide {
list-style: none;
flex: 0 0 84%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
&::scroll-marker-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding-block: 1.25rem 0.5rem;
}
> .slide::scroll-marker {
content: "" / attr(data-marker-label);
inline-size: 20px;
block-size: 20px;
border: 2px solid #64748b;
border-radius: 50%;
background: #fff;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, scale 0.2s;
}
> .slide::scroll-marker:hover {
border-color: #7c5cff;
background: color-mix(in oklch, #7c5cff 18%, #fff);
}
> .slide::scroll-marker:focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 38%, transparent);
outline-offset: 2px;
}
> .slide::scroll-marker:target-current {
border-color: #172033;
background: #172033;
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #172033;
scale: 1.1;
}
&::scroll-button(*) {
position: absolute;
position-anchor: --carousel;
z-index: 1;
display: grid;
place-items: center;
inline-size: 2.5rem;
block-size: 2.5rem;
border: 0;
border-radius: 50%;
background: #172033;
color: #fff;
font: inherit;
font-size: 1.15rem;
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 0.24);
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s, scale 0.2s;
}
&::scroll-button(*):hover {
background: #7c5cff;
scale: 1.08;
}
&::scroll-button(*):focus-visible {
outline: 3px solid color-mix(in oklch, #7c5cff 42%, transparent);
outline-offset: 3px;
}
&::scroll-button(*):disabled {
opacity: 0.35;
cursor: not-allowed;
}
&::scroll-button(inline-start) {
content: "←";
position-area: left center;
translate: 60%;
}
&::scroll-button(inline-end) {
content: "→";
position-area: right center;
translate: -60%;
}
@media (prefers-reduced-motion: reduce) {
scroll-behavior: auto;
}
}
.slide-card {
box-sizing: border-box;
display: grid;
align-content: end;
gap: 0.5rem;
min-block-size: 18rem;
padding: clamp(1.5rem, 5vw, 3rem);
border: 1px solid color-mix(in oklch, var(--slide-accent) 72%, #172033);
border-radius: 1.5rem;
background: var(--slide-accent);
color: #172033;
> * {
margin: 0;
}
h2 {
max-inline-size: 16ch;
font-size: clamp(1.75rem, 6vw, 3.5rem);
line-height: 1;
text-wrap: balance;
}
}
.slide-eyebrow {
color: rgb(23 32 51 / 0.72);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}Guía educativa de Carrusel CSS
Un carrusel combina contenido en HTML, posiciones de descanso y controles para recorrerlo. Aprende a añadir botones y marcadores nativos sin perder la lectura ni la navegación cuando esas funciones no están disponibles.
Scroll snap resuelve la alineación. El carrusel añade una interfaz para avanzar, volver y elegir un destino.
Empieza con una lista de contenido en orden de lectura y un contenedor con overflow: auto. Flexbox distribuye las tarjetas; scroll-snap-type y scroll-snap-align definen dónde descansan. Las tarjetas siguen en el documento aunque estén fuera de la parte visible.
Consultar el modelo, los ejes y las intensidades de scroll-snapLos controles son otra capa: el navegador puede generar botones sobre el contenedor y un marcador por tarjeta. No necesitas duplicar las tarjetas ni moverlas con transform. Desplazarse, seleccionar un marcador y mover el foco son acciones distintas.
Generar un control, colocarlo y darle un nombre son responsabilidades diferentes.
::scroll-button(inline-start) y ::scroll-button(inline-end) generan botones cuando content no es none. En un modo horizontal LTR sirven para retroceder y avanzar; un carrusel vertical usa block-start y block-end. ::scroll-button(*) permite compartir estilos, y :disabled aparece automáticamente al alcanzar un extremo.
Un botón desplaza aproximadamente una página del contenedor y aplica las reglas de snap. No equivale siempre a sumar una tarjeta: el ancho, el gap y cuántas tarjetas caben cambian el resultado. Esta receta usa una tarjeta por vista; comprueba el recorrido de extremo a extremo si modificas su tamaño.
scroll-marker-group: after habilita el grupo y lo sitúa después en el orden de cajas y de navegación. ::scroll-marker-group distribuye los controles; cada li::scroll-marker los genera con content. before coloca el grupo antes: haz coincidir su posición visual y su orden de foco.
::scroll-marker:target-current selecciona el marcador que el navegador considera actual según el scroll. No es :focus-visible ni el :target de la URL. En el ejemplo, el subrayado distingue el destino actual y un contorno discontinuo distingue el foco.
Usa content: "→" / "Avanzar" para separar el símbolo de su nombre accesible. Los marcadores leen un número visible y una alternativa específica desde atributos data-* localizados; los enlaces HTML muestran los títulos completos. content: "" sin nombre no explica el destino. Comprueba su anuncio real: si el navegador no expone la alternativa, los enlaces de respaldo conservan destinos identificables.
La posición del grupo y su patrón de navegación se eligen por separado.
Combina before o after con links o tabs: scroll-marker-group: after links y scroll-marker-group: before tabs. before/after colocan el grupo; links/tabs cambian su semántica y teclado. Omitir el modo usa links en una implementación de la sintaxis actual. none desactiva el grupo y no se combina con esos valores.
links genera un grupo con rol navigation y marcadores con rol link. Todos los marcadores entran en el recorrido de Tab. Activarlos dirige el foco al destino, como un enlace a un fragmento. Es adecuado para navegar una colección cuyo contenido debe seguir disponible en el árbol de accesibilidad.
tabs genera roles tablist, tab y tabpanel. Solo el marcador activo es una parada de Tab; las flechas recorren los marcadores y la activación conserva el foco en la pestaña. Tab continúa en el contenido del panel activo. El navegador oculta los paneles inactivos del árbol de accesibilidad: no elijas este modo solo por la apariencia de los puntos.
Chrome anuncia ambos modos desde Chrome 154. Detecta el valor completo, no solo scroll-marker-group: after. Estas dos recetas conservan enlaces HTML como respaldo y solo los ocultan cuando se acepta el modo explícito junto con ::scroll-marker. Si falta soporte, se muestran los enlaces y los controles posicionales que el navegador pueda generar; esa versión no demuestra el patrón links/tabs solicitado. Comprueba teclado y árbol de accesibilidad en el navegador real.
Escribe una lista ordenada por intención de lectura. El contenido debe seguir siendo útil sin controles generados.
Define el scroll en el contenedor y la alineación en cada tarjeta. Prueba también la primera y la última.
Añade botones de dirección y un marcador por destino, con nombres que expliquen cada acción.
Escribe una lista ordenada por intención de lectura. El contenido debe seguir siendo útil sin controles generados.
Define el scroll en el contenedor y la alineación en cada tarjeta. Prueba también la primera y la última.
Añade botones de dirección y un marcador por destino, con nombres que expliquen cada acción.
Compara la misma colección con enlaces HTML y con controles CSS adicionales.
El HTML y el CSS mostrados alimentan cada resultado. La base conserva barra de scroll, foco y enlaces a IDs únicos. Los enlaces siguen visibles también en la versión mejorada, para ofrecer una navegación explícita sin depender de la detección de los pseudo-elementos.
Escribe una lista ordenada por intención de lectura. El contenido debe seguir siendo útil sin controles generados.
Define el scroll en el contenedor y la alineación en cada tarjeta. Prueba también la primera y la última.
Añade botones de dirección y un marcador por destino, con nombres que expliquen cada acción.
Escribe una lista ordenada por intención de lectura. El contenido debe seguir siendo útil sin controles generados.
Define el scroll en el contenedor y la alineación en cada tarjeta. Prueba también la primera y la última.
Añade botones de dirección y un marcador por destino, con nombres que expliquen cada acción.
La receta coloca los controles debajo mediante el grid del envoltorio: no necesita posicionamiento por anclas. Los botones y el grupo generan cajas hermanas del contenedor según CSS Overflow 5. Si solo se soportan marcadores, el grupo conserva su disposición de flujo; cada mejora se comprueba por separado.
CSS reduce el trabajo del controlador, pero no sustituye la comprobación de la experiencia completa.
Prueba Tab y Shift + Tab, activa botones con Enter o Espacio y comprueba todos los enlaces. Las flechas pueden desplazar la lista enfocada o recorrer el grupo nativo de marcadores, según su modo y el navegador. No añadas manejadores que capturen esas teclas sobre enlaces o campos.
Elige links o tabs según la intención del contenido y verifica soporte por valor. Las recetas iniciales usan after; las comparaciones de modos añaden explícitamente links o tabs. El navegador asigna los roles: no añadas role="tablist" al HTML para imitar soporte que falta.
Posición visible no significa foco. Mantén los nombres de las tarjetas, el orden HTML y el acceso al contenido fuera de vista. No apliques aria-hidden ni inert a una tarjeta solo porque no está centrada. Verifica el nombre, rol y estado de los controles generados con tu combinación de navegador y lector de pantalla.
La receta no avanza automáticamente. scroll-behavior: smooth mejora algunos desplazamientos y vuelve a auto con prefers-reduced-motion: reduce. Si tu producto incorpora reproducción automática, necesita control de pausa y detenerse al entrar foco o puntero, conforme al patrón de carrusel de WAI-ARIA.
Para una lista breve que debe verse de un vistazo, puede convenir un grid. Para una galería desplazable sin navegación adicional, basta la base de snap. Elige un carrusel cuando recorrer destinos aporte utilidad y puedas mantener acceso claro a todo el contenido.
Diagnostica cada capa antes de atribuir un fallo al carrusel completo.
Evalúa las piezas del carrusel por separado, sin extender el estado de snap a la navegación generada.
Scroll snap está ampliamente disponible. MDN sigue clasificando ::scroll-button(), ::scroll-marker, scroll-marker-group y :target-current como funciones experimentales de disponibilidad limitada en la consulta del 13 de octubre de 2026. Revisa sus tablas y las tarjetas de cada feature antes de decidir qué necesita tu producto.
@supports comprueba sintaxis, no el resultado visual, el patrón de teclado ni los anuncios de un lector de pantalla. La receta separa las comprobaciones de botones y marcadores, mantiene enlaces y no oculta la barra. Ignorar una mejora debe dejar contenido y navegación útiles.
Si necesitas botones personalizados o un estado anunciado con el mismo comportamiento en todos tus navegadores objetivo, usa controles HTML nativos y un controlador probado. No presentes una detección positiva o esta preview como garantía de accesibilidad o compatibilidad entre motores.