Personalizando el temido ScrollBar de JavaFX: De feo a invisible estilo Mac

Aprende a modificar las clases CSS internas .scroll-bar, .thumb, y .track para crear un scrollbar minimalista que se desvanece o se muestra elegante como en MacOS.

J

JJ Arroyo

2 de marzo de 2026 5 min de lectura

Personalizando el temido ScrollBar de JavaFX: De feo a invisible estilo Mac

Si has trabajado con el ScrollPane o el TableView de JavaFX, conoces el problema: El ScrollBar por defecto es tosco, gris, ocupa demasiado espacio y grita "aplicación de escritorio del año 2005".

En la era moderna del diseño UI, impulsada por sistemas como macOS y librerías móviles, el scrollbar ideal es invisible cuando no se necesita, y delgado, superpuesto y sin botones de flechas cuando estás haciendo scroll.

En este artículo, vamos a "hackear" la estructura interna del ScrollBar de JavaFX usando puramente CSS para lograr ese aspecto flotante y estilizado.

Estructura interna del ScrollBar

Antes de lanzar CSS a lo loco, debemos entender cómo JavaFX arma su ScrollBar. Está compuesto por sub-nodos:

  1. .scroll-bar (El contenedor principal).
  2. .track (La pista por donde corre la barra).
  3. .thumb (El "pulgar" o barra que tú arrastras).
  4. .increment-button / .decrement-button (Los horribles botones con flechitas en los extremos).
  5. .increment-arrow / .decrement-arrow (El icono dentro de los botones).

Nuestro objetivo será aplastar lo que no sirve (los botones de flechas y el fondo de la pista) y darle un bonito borde redondeado al thumb.

1. El CSS Mágico (Cópialo y pégalo)

Crea un archivo llamado mac-scrollbar.css en tu proyecto y añade las siguientes reglas:

/* mac-scrollbar.css */

/* 1. Desaparecer los botones de flecha completamente */
.scroll-pane > .scroll-bar:vertical > .increment-button,
.scroll-pane > .scroll-bar:vertical > .decrement-button,
.scroll-pane > .scroll-bar:horizontal > .increment-button,
.scroll-pane > .scroll-bar:horizontal > .decrement-button {
    -fx-padding: 0;
    -fx-setminsize: 0;
    -fx-setprefsize: 0;
    -fx-setmaxsize: 0;
}

.scroll-pane > .scroll-bar:vertical > .increment-button > .increment-arrow,
.scroll-pane > .scroll-bar:vertical > .decrement-button > .decrement-arrow,
.scroll-pane > .scroll-bar:horizontal > .increment-button > .increment-arrow,
.scroll-pane > .scroll-bar:horizontal > .decrement-button > .decrement-arrow {
    -fx-padding: 0;
    -fx-shape: null;
}

/* 2. Transparentar y adelgazar la pista (Track) */
.scroll-pane > .scroll-bar:vertical > .track {
    -fx-background-color: transparent;
    -fx-border-color: transparent;
    -fx-background-radius: 0;
}

.scroll-pane > .scroll-bar:horizontal > .track {
    -fx-background-color: transparent;
    -fx-border-color: transparent;
    -fx-background-radius: 0;
}

/* Reducir el grosor total de la barra del ScrollPane */
.scroll-pane > .scroll-bar:vertical {
    -fx-background-color: transparent;
    -fx-pref-width: 8px; /* Qué tan ancho será el scroll */
}

.scroll-pane > .scroll-bar:horizontal {
    -fx-background-color: transparent;
    -fx-pref-height: 8px; /* Qué tan alto será el scroll */
}

/* 3. Estilizar el "Thumb" (Lo que arrastras) */
.scroll-pane > .scroll-bar > .thumb {
    -fx-background-color: transparent; /* Transparente por defecto para que sea invisible */
    -fx-background-radius: 10px; /* Redondeado estilo píldora */
    -fx-background-insets: 0;
}

/* 4. Efecto de Hover interactivo (Solo aparece si pones el cursor SOBRE el borde) */
.scroll-pane > .scroll-bar:hover > .thumb {
    -fx-background-color: rgba(
        150,
        150,
        150,
        0.4
    ); /* Aparece sutil al pasar sobre el track */
}

.scroll-pane > .scroll-bar > .thumb:hover {
    -fx-background-color: rgba(
        150,
        150,
        150,
        0.7
    ); /* Se oscurece más al tocar el Thumb */
}

.scroll-pane > .scroll-bar > .thumb:pressed {
    -fx-background-color: rgba(
        100,
        100,
        100,
        0.9
    ); /* Más oscuro al hacer clic */
}

/* (Opcional) Transparentar el cuadrito que une ambas barras en la esquina inferior derecha */
.scroll-pane > .corner {
    -fx-background-color: transparent;
}

2. Aplicándolo a JavaFX (App de Ejemplo)

Para ver el milagro en acción, necesitamos un contenedor que soporte scroll, típicamente el ScrollPane o el ListView.

Aplica tu CSS a un layout principal y mira cómo la magia fluye:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class MacScrollDemo extends Application {

    @Override
    public void start(Stage primaryStage) {
        // Contenido muy largo para forzar el Scroll
        VBox content = new VBox(20);
        content.setPadding(new Insets(30));
        content.setAlignment(Pos.CENTER);
        content.setStyle("-fx-background-color: #1a1a2e;");

        for (int i = 1; i <= 50; i++) {
            Label label = new Label("Contenido elegante n° " + i);
            label.setStyle(
                "-fx-text-fill: #e94560;" +
                "-fx-font-size: 18px;" +
                "-fx-font-weight: bold;" +
                "-fx-padding: 20px 40px;" +
                "-fx-background-color: #16213e;" +
                "-fx-background-radius: 10;"
            );
            label.setPrefWidth(400);
            label.setAlignment(Pos.CENTER);
            content.getChildren().add(label);
        }

        // El ScrollPane que vamos a tunear
        ScrollPane scrollPane = new ScrollPane(content);
        scrollPane.setFitToWidth(true);

        // Quitar bordes feos del propio ScrollPane
        scrollPane.setStyle(
            "-fx-background-color: transparent;" +
            "-fx-background: transparent;" +
            "-fx-padding: 0;" +
            "-fx-background-insets: 0;"
        );

        Scene scene = new Scene(scrollPane, 500, 600);

        // ¡Agregar el archivo CSS aquí!
        // scene.getStylesheets().add(getClass().getResource("mac-scrollbar.css").toExternalForm());

        primaryStage.setTitle("JavaFX Mac-Style Scrollbar");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Bonus Pro: Superponiendo el Scroll sobre el contenido

Si quieres que estilo macOS sea perfecto, el scrollbar no debería empujar el contenido hacia la izquierda, sino estar pegado encima de él (Overlay).

Desafortunadamente, el ScrollPane por defecto ocupa espacio físico real (Layout Bounds) en el SceneGraph de JavaFX. Para forzar un Overlay verdadero en JavaFX:

  1. Ocultamos el scroll por defecto (-fx-pref-width: 0).
  2. Creamos una barra (Rectangle) flotante sobre un StackPane.
  3. Conectamos la propiedad vvalueProperty() del ScrollPane a la posición de nuestra barra personalizada.

Bonus Pro 2: ¡El Scrollbar que solo aparece al mover la rueda! (Comportamiento 100% macOS)

Si probaste el código anterior, notarás que el scrollbar es invisible, y solo aparece cuando pones el ratón justo sobre la zona derecha de la pantalla.

Pero en una Mac real, el scrollbar aparece automáticamente cuando haces scroll (incluso si tu ratón está en el centro de la pantalla), y luego se desvanece suavemente si dejas de mover la rueda.

El CSS solo reacciona a los movimientos del ratón (:hover), no al evento de "estar haciendo scroll".

Para lograr el verdadero comportamiento Premium, necesitamos una inyección pequeñita de código Java que active una clase CSS personalizada (:scrolling) temporalmente.

1. El Nuevo CSS

Modifica la sección del "Thumb" en tu CSS para que soporte el estado :scrolling:

/* Thumb invisible por defecto */
.scroll-bar > .thumb {
    -fx-background-color: transparent;
    -fx-background-radius: 10px;
    -fx-background-insets: 0;
}

/* Aparece SOLAMENTE cuando Java activa el estado :scrolling, O cuando pones el mouse encima */
.scroll-pane:scrolling > .scroll-bar > .thumb,
.scroll-bar:hover > .thumb {
    -fx-background-color: rgba(150, 150, 150, 0.4);
}

.scroll-bar > .thumb:hover {
    -fx-background-color: rgba(150, 150, 150, 0.7);
}

.scroll-bar > .thumb:pressed {
    -fx-background-color: rgba(100, 100, 100, 0.9);
}

2. La Magia en Java (Fade In/Out on Scroll)

Agrega este fragmento de código a tu controlador o clase de vista, justo después de instanciar tu ScrollPane:

import javafx.css.PseudoClass;
import javafx.animation.PauseTransition;
import javafx.util.Duration;

// ...

// 1. Creamos nuestra propia PseudoClase CSS personalizada
PseudoClass scrollingClass = PseudoClass.getPseudoClass("scrolling");

// 2. Un temporizador que apaga el ScrollBar después de 1 segundo inactivo
PauseTransition hideTimer = new PauseTransition(Duration.seconds(1));
hideTimer.setOnFinished(e -> scrollPane.pseudoClassStateChanged(scrollingClass, false));

// 3. Cada vez que el scroll se mueve (vvalue cambia), prendemos la clase CSS y reiniciamos el reloj
scrollPane.vvalueProperty().addListener((obs, oldVal, newVal) -> {
    scrollPane.pseudoClassStateChanged(scrollingClass, true);
    hideTimer.playFromStart();
});

¡Boom! Ahora tu ScrollBar está completamente invisible mientras lees. Cuando muevas la rueda del ratón, el scrollbar se iluminará en el borde derecho permitiéndote ver por dónde vas, y 1 segundo después de soltar la rueda, se desvanecerá elegantemente. ¡Puro diseño Apple en JavaFX!

forumComentarios

Deja tu comentario

progress_activityCargando comentarios...