wandres.dev
SCROLL · Snap, overscroll y barras

scrollbar-gutter, scrollbar-width y scrollbar-color

Reservar el espacio de la barra para que el contenido no salte, y estilar la barra con las tres propiedades estándar en lugar de con los pseudo-elementos heredados.

⏱ 16 min

Cuando el contenido de una página crece lo suficiente para provocar una barra de desplazamiento, esa barra ocupa espacio y el contenido se estrecha de golpe: los elementos centrados se desplazan unos píxeles y el layout entero da un tirón. Es un salto que se ve todos los días y que hoy se resuelve con una declaración. Y para el aspecto de la barra existen tres propiedades estándar que sustituyen a la familia ::-webkit-scrollbar, con una diferencia importante: las estándar se llevan bien con el modo oscuro y con la accesibilidad.

🎯 Al terminar esta lección sabrás
  • Reservar el espacio de la barra con scrollbar-gutter.
  • Ajustar el grosor con scrollbar-width y el color con scrollbar-color.
  • Conocer el soporte de cada una y su relación con los pseudo-elementos heredados.
  • Evitar los errores de contraste al colorear una barra.

scrollbar-gutter

Reserva el hueco de la barra de desplazamiento aunque la barra no esté visible, con lo que el ancho disponible para el contenido no cambia cuando aparece.

html { scrollbar-gutter: stable; }

Los valores son:

  • auto. El comportamiento por defecto: el hueco existe solo cuando hay barra.
  • stable. El hueco se reserva siempre, en el borde final del eje en línea.
  • stable both-edges. El hueco se reserva en los dos bordes, de modo que el contenido queda simétrico. Es lo que quieres cuando el contenedor tiene contenido centrado y la asimetría de unos pocos píxeles se nota.

Está en Chrome 94, Firefox 97 y Safari 18.2.

El caso de uso principal es el elemento raíz: una página cuyo contenido a veces cabe y a veces no —porque un filtro reduce la lista, porque un acordeón se cierra— salta lateralmente cada vez. scrollbar-gutter: stable en html lo elimina.

El segundo caso es cualquier panel con overflow: auto cuyo contenido varía. La barra que aparece y desaparece hace que el contenido se reajuste, y con textos largos eso significa reflujo de líneas.

Un apunte importante: en plataformas donde las barras son superpuestas —el comportamiento por defecto de macOS, y de iOS y Android— la barra no ocupa espacio y no hay salto que corregir. scrollbar-gutter: stable en esos casos no reserva nada, porque no hay nada que reservar. La declaración no hace daño; simplemente no se nota. El problema es real en Windows, en Linux y en macOS con la opción de mostrar siempre las barras activada.

scrollbar-width y scrollbar-color

Son las dos propiedades estándar para el aspecto.

scrollbar-width acepta auto, thin y none. No acepta longitudes: no puedes pedir una barra de seis píxeles, porque la anchura la decide la plataforma y la propiedad solo elige entre las variantes que esa plataforma ofrece.

.panel { scrollbar-width: thin; }

none oculta la barra manteniendo el scroll funcional. Úsalo con criterio: una superficie que se desplaza y no lo indica es un problema de descubribilidad, salvo que haya otra señal —un carrusel con puntos de paginación, un panel con flechas—.

Está en Chrome 121, Firefox 64 y Safari 18.2.

scrollbar-color acepta dos colores: primero el del pulgar, después el de la vía.

.panel {
  scrollbar-color: color-mix(in oklch, CanvasText 40%, transparent) transparent;
}

Aquí hay que ser preciso con el soporte porque las dos propiedades no llegaron juntas: scrollbar-color está en Chrome 121 y Firefox 64, pero en Safari desde la 26.2, no la 18.2. Es una diferencia de un año largo respecto a scrollbar-width.

El valor auto devuelve la barra al aspecto del sistema, que es lo que quieres cuando el usuario tiene un tema de alto contraste activo.

@media (forced-colors: active) {
  .panel { scrollbar-color: auto; }
}

Los pseudo-elementos heredados

::-webkit-scrollbar y su familia —::-webkit-scrollbar-thumb, ::-webkit-scrollbar-track y varios más— siguen funcionando en los motores basados en Blink y en WebKit. No son estándar, nunca lo fueron, y Firefox no los soporta.

Su ventaja sobre las propiedades estándar es el control: puedes dar bordes redondeados al pulgar, márgenes, sombras, tamaños en píxeles. Su desventaja es que al usarlos sustituyes por completo el dibujo de la barra por el tuyo, lo que significa que asumes la responsabilidad de que siga siendo visible en modo oscuro, en alto contraste y con las preferencias de accesibilidad del sistema. La mayoría de las barras personalizadas que se ven por ahí fallan en al menos uno de los tres.

La estrategia sensata en 2026 es usar las estándar como base y, si de verdad hace falta más control, añadir los pseudo-elementos como mejora dentro de un @supports:

.panel {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklch, CanvasText 40%, transparent) transparent;
}

@supports selector(::-webkit-scrollbar) {
  .panel::-webkit-scrollbar { inline-size: 8px; }
  .panel::-webkit-scrollbar-thumb {
    background: color-mix(in oklch, CanvasText 40%, transparent);
    border-radius: 4px;
  }
  .panel::-webkit-scrollbar-track { background: transparent; }
}

Ojo con un detalle: en Chromium, declarar scrollbar-width o scrollbar-color desactiva los pseudo-elementos sobre ese mismo elemento. Las dos formas no se combinan; gana la estándar. Eso hace que el ejemplo de arriba sea seguro por accidente en Chromium y útil en Safari, donde scrollbar-color llegó tarde.

flowchart TB
A[Quieres tocar la barra de desplazamiento] --> B{Que necesitas}
B -->|Que el contenido no salte| C[scrollbar-gutter stable]
C --> D{El contenido esta centrado}
D -->|Si| E[stable both-edges para mantener la simetria]
B -->|Que ocupe menos| F[scrollbar-width thin]
B -->|Otro color| G[scrollbar-color pulgar y via]
G --> H[Devuelve auto en forced-colors active]
B -->|Control total de la forma| I[Pseudo-elementos webkit como mejora]
I --> J[Asumes tu la visibilidad en oscuro y alto contraste]
style A fill:#89b4fa,color:#11111b
style C fill:#a6e3a1,color:#11111b
style E fill:#a6e3a1,color:#11111b
style H fill:#a6e3a1,color:#11111b
style J fill:#f9e2af,color:#11111b

Contraste y tamaño mínimo

Dos criterios que conviene respetar al colorear una barra.

Contraste. El pulgar es un control de interfaz, no decoración. Las pautas de accesibilidad piden una relación de contraste de al menos 3 a 1 entre un componente de interfaz y lo que tiene al lado. Un pulgar gris muy claro sobre fondo blanco no lo cumple, por elegante que parezca.

Tamaño. scrollbar-width: thin produce una barra que en algunos sistemas ronda los ocho píxeles. Como objetivo de puntero, eso está por debajo de lo cómodo para muchos usuarios. En una superficie donde arrastrar la barra sea la forma principal de navegar, auto es la elección correcta; thin está bien en paneles secundarios donde el gesto o la rueda son la vía principal.

Y una regla que ahorra discusiones: la barra del documento se toca lo mínimo. Los usuarios la reconocen, la usan y esperan que se comporte como la de su sistema. Personalizar barras tiene sentido en paneles internos, no en el scroll principal de la página.

scrollbar-gutter existe porque la alternativa obligaba a mentir sobre el ancho del viewport

Antes de scrollbar-gutter, la técnica para evitar el salto era declarar overflow-y: scroll en html, lo que fuerza la barra a estar siempre presente. Funciona, y a cambio deja una barra permanentemente visible y desactivada en las páginas cortas, cosa que nadie quiere. Pero el problema de fondo era otro y es más interesante: la anchura del viewport que ven las unidades y las consultas de medios no coincide con la que ve el contenido cuando hay barra. Las unidades vw y las consultas @media (width: ...) se resuelven contra el viewport incluyendo el espacio de la barra clásica, mientras que un inline-size: 100% se resuelve contra el bloque contenedor, que excluye ese espacio. De ahí sale un bug clásico que aparece cada pocos meses en cualquier base de código grande: un elemento con inline-size: 100vw dentro de una página con barra clásica es unos quince píxeles más ancho que su contenedor, provoca desbordamiento horizontal, y alguien lo tapa con overflow-x: hidden, que a su vez rompe todos los position: sticky de la página como vimos en el nivel anterior. Una cadena de tres decisiones razonables que acaba en un fallo que nadie relaciona con su causa. scrollbar-gutter: stable corta la cadena en el primer eslabón: con el hueco reservado siempre, la diferencia entre las dos anchuras desaparece y 100vw vuelve a significar lo que parece. Es un buen ejemplo de que la propiedad que arregla un salto visual está en realidad arreglando una inconsistencia del modelo.

⚔️ Elimina el salto
  1. Monta una página cuyo contenido pase de caber a no caber con un filtro y observa el salto lateral en Windows o con las barras siempre visibles.
  2. Añade scrollbar-gutter: stable en html y comprueba que desaparece.
  3. Compara stable con stable both-edges en una página con contenido centrado.
  4. Aplica scrollbar-width: thin y scrollbar-color a un panel y mide el contraste del pulgar contra el fondo.
  5. Comprueba en Safari y en Chrome si scrollbar-color surte efecto y si desactiva los pseudo-elementos.