Tutorial: Dashboard Profesional en JavaFX
Construye paso a paso un dashboard moderno con sidebar, stat cards animadas, gráfico de barras y tabla de actividad. Diseño dark premium con componentes modulares reutilizables.
JavaFX Master
5 de marzo de 2025 • 25 min de lectura
En este tutorial construiremos un dashboard profesional completo desde cero con un diseño dark premium. El proyecto es completamente modular — cada componente (sidebar, topbar, tarjetas, tabla) vive en su propio archivo Java.
Al final podrás descargar el proyecto completo como un ZIP listo para ejecutar.
Resultado Final
El dashboard incluye:
- Sidebar con navegación y perfil de usuario
- Top Bar con búsqueda y avatar
- 4 Stat Cards con contadores animados
- Gráfico de barras de ventas mensuales
- Panel de resumen rápido
- Tabla de actividad reciente
Descargar Proyecto Completo
Descargar dashboard-javafx.zipEstructura del Proyecto
El proyecto está diseñado de forma modular para mantener cada archivo enfocado y manejable:
dashboard-javafx/
├── src/main/java/com/dashboard/
│ ├── App.java # Punto de entrada
│ ├── Sidebar.java # Barra lateral de navegación
│ ├── TopBar.java # Barra superior
│ ├── StatCard.java # Tarjeta de estadísticas
│ ├── ChartPanel.java # Panel de gráfico de barras
│ ├── ActivityTable.java # Tabla de actividad reciente
│ └── DashboardView.java # Vista principal que compone todo
├── src/main/resources/css/
│ └── dashboard.css # Todos los estilos
└── run.bat # Compilar y ejecutar
Paso 1: App.java — Punto de Entrada
El punto de entrada es simple: crea un BorderPane con el sidebar a la izquierda y el contenido principal al centro.
package com.dashboard;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
root.getStyleClass().add("root-container");
// Sidebar a la izquierda
Sidebar sidebar = new Sidebar();
root.setLeft(sidebar);
// Contenido principal (TopBar + Dashboard)
BorderPane mainContent = new BorderPane();
mainContent.getStyleClass().add("main-content");
TopBar topBar = new TopBar();
mainContent.setTop(topBar);
DashboardView dashboard = new DashboardView();
mainContent.setCenter(dashboard);
root.setCenter(mainContent);
Scene scene = new Scene(root, 1280, 720);
scene.getStylesheets().add(
getClass().getResource("/css/dashboard.css").toExternalForm()
);
stage.setTitle("Dashboard JavaFX");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Concepto clave: Usamos BorderPane como layout raíz porque nos permite colocar el sidebar en LEFT y el contenido principal en CENTER. El contenido principal a su vez es otro BorderPane con el topbar en TOP y el dashboard scrolleable en CENTER.
Paso 2: Sidebar.java — Navegación Lateral
El sidebar contiene el logo, dos secciones de menú y un perfil de usuario al fondo:
package com.dashboard;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
public class Sidebar extends VBox {
private String activeItem = "Dashboard";
public Sidebar() {
getStyleClass().add("sidebar");
setPrefWidth(240);
setMinWidth(240);
// Logo
Label logo = new Label("⚡ Dashboard");
logo.getStyleClass().add("sidebar-logo");
// Sección principal
Label seccionPrincipal = new Label("PRINCIPAL");
seccionPrincipal.getStyleClass().add("sidebar-section");
VBox menuPrincipal = new VBox(2);
menuPrincipal.getChildren().addAll(
crearMenuItem("📊", "Dashboard", true),
crearMenuItem("📈", "Analíticas", false),
crearMenuItem("👥", "Usuarios", false),
crearMenuItem("📦", "Productos", false)
);
// Sección gestión
Label seccionGestion = new Label("GESTIÓN");
seccionGestion.getStyleClass().add("sidebar-section");
VBox menuGestion = new VBox(2);
menuGestion.getChildren().addAll(
crearMenuItem("💰", "Ventas", false),
crearMenuItem("📋", "Reportes", false),
crearMenuItem("⚙️", "Configuración", false)
);
// Espaciador para empujar el perfil al fondo
Region spacer = new Region();
VBox.setVgrow(spacer, Priority.ALWAYS);
// Perfil de usuario
VBox perfil = crearPerfil();
getChildren().addAll(
logo,
seccionPrincipal, menuPrincipal,
seccionGestion, menuGestion,
spacer,
perfil
);
}
private VBox crearMenuItem(String icono, String texto, boolean activo) {
Label iconLabel = new Label(icono);
iconLabel.getStyleClass().add("menu-icon");
Label textLabel = new Label(texto);
textLabel.getStyleClass().add("menu-text");
VBox item = new VBox();
item.getStyleClass().add("menu-item");
if (activo) {
item.getStyleClass().add("menu-item-active");
}
javafx.scene.layout.HBox hbox = new javafx.scene.layout.HBox(10);
hbox.setAlignment(Pos.CENTER_LEFT);
hbox.getChildren().addAll(iconLabel, textLabel);
item.getChildren().add(hbox);
item.setOnMouseClicked(e -> {
item.getStyleClass().add("menu-item-active");
activeItem = texto;
});
return item;
}
private VBox crearPerfil() {
VBox perfil = new VBox(4);
perfil.getStyleClass().add("sidebar-profile");
javafx.scene.layout.HBox hbox = new javafx.scene.layout.HBox(10);
hbox.setAlignment(Pos.CENTER_LEFT);
Label avatar = new Label("JA");
avatar.getStyleClass().add("profile-avatar");
VBox info = new VBox(2);
Label nombre = new Label("Jorge Arroyo");
nombre.getStyleClass().add("profile-name");
Label rol = new Label("Administrador");
rol.getStyleClass().add("profile-role");
info.getChildren().addAll(nombre, rol);
hbox.getChildren().addAll(avatar, info);
perfil.getChildren().add(hbox);
return perfil;
}
}
Técnicas importantes:
VBox.setVgrow(spacer, Priority.ALWAYS)empuja el perfil de usuario hasta el fondogetStyleClass().add()aplica las clases CSS que definiremos más adelante- El item activo se resalta con la clase
menu-item-active
Paso 3: TopBar.java — Barra Superior
Una barra con el título de la página, un campo de búsqueda y el avatar del usuario:
package com.dashboard;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
public class TopBar extends HBox {
public TopBar() {
getStyleClass().add("topbar");
setAlignment(Pos.CENTER_LEFT);
setSpacing(16);
// Título de la página
Label titulo = new Label("Dashboard");
titulo.getStyleClass().add("topbar-title");
Label subtitulo = new Label("Resumen general del sistema");
subtitulo.getStyleClass().add("topbar-subtitle");
javafx.scene.layout.VBox tituloBox = new javafx.scene.layout.VBox(2);
tituloBox.getChildren().addAll(titulo, subtitulo);
// Espaciador
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
// Barra de búsqueda
TextField buscar = new TextField();
buscar.setPromptText("🔍 Buscar...");
buscar.getStyleClass().add("topbar-search");
buscar.setPrefWidth(220);
// Notificaciones
Label notificaciones = new Label("🔔");
notificaciones.getStyleClass().add("topbar-icon");
// Avatar
Label avatar = new Label("JA");
avatar.getStyleClass().add("topbar-avatar");
getChildren().addAll(tituloBox, spacer, buscar, notificaciones, avatar);
}
}
Paso 4: StatCard.java — Tarjetas con Contadores Animados
Cada tarjeta muestra un KPI con un contador que se anima de 0 al valor final:
package com.dashboard;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
public class StatCard extends VBox {
public StatCard(String titulo, int valor, String formato, String tendencia,
boolean positiva, String icono, String colorClass) {
getStyleClass().addAll("stat-card", colorClass);
setSpacing(8);
// Header: título + icono
HBox header = new HBox();
header.setAlignment(Pos.CENTER_LEFT);
Label tituloLabel = new Label(titulo);
tituloLabel.getStyleClass().add("stat-title");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label iconoLabel = new Label(icono);
iconoLabel.getStyleClass().add("stat-icon");
header.getChildren().addAll(tituloLabel, spacer, iconoLabel);
// Valor animado
IntegerProperty valorProp = new SimpleIntegerProperty(0);
Label valorLabel = new Label("0");
valorLabel.getStyleClass().add("stat-value");
valorProp.addListener((obs, oldVal, newVal) -> {
valorLabel.setText(String.format(formato, newVal.intValue()));
});
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(valorProp, 0)),
new KeyFrame(Duration.millis(1200), new KeyValue(valorProp, valor))
);
timeline.play();
// Tendencia
Label tendenciaLabel = new Label(
(positiva ? "↑ " : "↓ ") + tendencia
);
tendenciaLabel.getStyleClass().add(
positiva ? "stat-trend-up" : "stat-trend-down"
);
getChildren().addAll(header, valorLabel, tendenciaLabel);
}
}
La animación funciona así:
- Creamos un
IntegerPropertyque empieza en 0 - Un listener actualiza el
Labelcada vez que cambia - Un
Timelineanima la propiedad de 0 al valor final en 1.2 segundos
Paso 5: ChartPanel.java — Gráfico de Barras
Un gráfico de barras simple usando Rectangle:
package com.dashboard;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
public class ChartPanel extends VBox {
public ChartPanel() {
getStyleClass().add("chart-card");
setSpacing(0);
// Header
HBox header = new HBox();
header.getStyleClass().add("card-header");
header.setAlignment(Pos.CENTER_LEFT);
Label titulo = new Label("Ventas Mensuales");
titulo.getStyleClass().add("card-title");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label periodo = new Label("Últimos 6 meses");
periodo.getStyleClass().add("card-subtitle");
header.getChildren().addAll(titulo, spacer, periodo);
// Gráfico de barras
HBox barras = new HBox(8);
barras.setAlignment(Pos.BOTTOM_CENTER);
barras.setPadding(new Insets(20, 16, 10, 16));
VBox.setVgrow(barras, Priority.ALWAYS);
String[] meses = {"Oct", "Nov", "Dic", "Ene", "Feb", "Mar"};
double[] valores = {65, 45, 80, 55, 90, 72};
double maxValor = 90;
for (int i = 0; i < meses.length; i++) {
barras.getChildren().add(crearBarra(meses[i], valores[i], maxValor));
}
getChildren().addAll(header, barras);
}
private VBox crearBarra(String mes, double valor, double maxValor) {
VBox columna = new VBox(4);
columna.setAlignment(Pos.BOTTOM_CENTER);
HBox.setHgrow(columna, Priority.ALWAYS);
double alturaMax = 180;
double altura = (valor / maxValor) * alturaMax;
Label valorLabel = new Label(String.format("$%.0fk", valor));
valorLabel.getStyleClass().add("chart-value");
Rectangle barra = new Rectangle();
barra.setWidth(40);
barra.setHeight(altura);
barra.setArcWidth(6);
barra.setArcHeight(6);
barra.getStyleClass().add("chart-bar");
Label mesLabel = new Label(mes);
mesLabel.getStyleClass().add("chart-label");
columna.getChildren().addAll(valorLabel, barra, mesLabel);
return columna;
}
}
Detalle clave: La altura de cada barra es proporcional al valor máximo usando (valor / maxValor) * alturaMax. Los Rectangle tienen arcWidth y arcHeight para esquinas redondeadas.
Paso 6: ActivityTable.java — Tabla de Actividad
Una tabla de actividad reciente con filas estilizadas:
package com.dashboard;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
public class ActivityTable extends VBox {
public ActivityTable() {
getStyleClass().add("activity-card");
setSpacing(0);
// Header
HBox header = new HBox();
header.getStyleClass().add("card-header");
header.setAlignment(Pos.CENTER_LEFT);
Label titulo = new Label("Actividad Reciente");
titulo.getStyleClass().add("card-title");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label verTodo = new Label("Ver todo →");
verTodo.getStyleClass().add("card-link");
header.getChildren().addAll(titulo, spacer, verTodo);
// Cabecera de tabla
HBox tableCabecera = crearFila(
"Usuario", "Acción", "Fecha", "Estado", true
);
// Filas de datos
VBox filas = new VBox(0);
filas.getChildren().addAll(
crearFila("Carlos López", "Compra #1042",
"Hace 5 min", "✅ Completado", false),
crearFila("Ana Martínez", "Registro nuevo",
"Hace 12 min", "🔵 Nuevo", false),
crearFila("Pedro García", "Compra #1041",
"Hace 23 min", "✅ Completado", false),
crearFila("María Torres", "Soporte ticket",
"Hace 45 min", "🟡 Pendiente", false),
crearFila("Luis Ramírez", "Compra #1040",
"Hace 1 hora", "✅ Completado", false),
crearFila("Sofia Mendez", "Devolución",
"Hace 2 horas", "🔴 Cancelado", false)
);
getChildren().addAll(header, tableCabecera, filas);
}
private HBox crearFila(String col1, String col2,
String col3, String col4, boolean esHeader) {
HBox fila = new HBox();
fila.getStyleClass().add(
esHeader ? "table-header" : "table-row"
);
fila.setAlignment(Pos.CENTER_LEFT);
fila.setPadding(new Insets(12, 16, 12, 16));
Label l1 = new Label(col1);
Label l2 = new Label(col2);
Label l3 = new Label(col3);
Label l4 = new Label(col4);
l1.setPrefWidth(160);
l2.setPrefWidth(160);
l3.setPrefWidth(120);
l4.setPrefWidth(140);
if (esHeader) {
l1.getStyleClass().add("table-header-text");
l2.getStyleClass().add("table-header-text");
l3.getStyleClass().add("table-header-text");
l4.getStyleClass().add("table-header-text");
} else {
l1.getStyleClass().add("table-cell-primary");
l2.getStyleClass().add("table-cell");
l3.getStyleClass().add("table-cell-muted");
l4.getStyleClass().add("table-cell");
}
fila.getChildren().addAll(l1, l2, l3, l4);
return fila;
}
}
Paso 7: DashboardView.java — Composición Final
Este archivo une todos los componentes en un layout scrolleable:
package com.dashboard;
import javafx.geometry.Insets;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class DashboardView extends ScrollPane {
public DashboardView() {
getStyleClass().add("dashboard-scroll");
setFitToWidth(true);
setHbarPolicy(ScrollBarPolicy.NEVER);
VBox contenido = new VBox(20);
contenido.setPadding(new Insets(24));
contenido.getStyleClass().add("dashboard-content");
// Fila de stat cards
HBox statsRow = crearStatCards();
// Fila de chart + resumen
HBox chartsRow = crearChartsRow();
// Tabla de actividad
ActivityTable activityTable = new ActivityTable();
contenido.getChildren().addAll(statsRow, chartsRow, activityTable);
setContent(contenido);
}
private HBox crearStatCards() {
HBox row = new HBox(16);
StatCard ventas = new StatCard(
"Ventas Totales", 24500, "$%,d",
"+12.5% vs mes pasado", true, "💰", "stat-blue"
);
StatCard usuarios = new StatCard(
"Usuarios Activos", 1847, "%,d",
"+8.2% vs mes pasado", true, "👥", "stat-green"
);
StatCard pedidos = new StatCard(
"Pedidos Nuevos", 356, "%,d",
"-3.1% vs mes pasado", false, "📦", "stat-amber"
);
StatCard tasa = new StatCard(
"Tasa de Conversión", 68, "%d%%",
"+5.4% vs mes pasado", true, "📈", "stat-violet"
);
HBox.setHgrow(ventas, Priority.ALWAYS);
HBox.setHgrow(usuarios, Priority.ALWAYS);
HBox.setHgrow(pedidos, Priority.ALWAYS);
HBox.setHgrow(tasa, Priority.ALWAYS);
row.getChildren().addAll(ventas, usuarios, pedidos, tasa);
return row;
}
private HBox crearChartsRow() {
HBox row = new HBox(16);
ChartPanel chart = new ChartPanel();
HBox.setHgrow(chart, Priority.ALWAYS);
VBox resumen = crearResumen();
resumen.setPrefWidth(280);
resumen.setMinWidth(280);
row.getChildren().addAll(chart, resumen);
return row;
}
private VBox crearResumen() {
VBox card = new VBox(12);
card.getStyleClass().add("summary-card");
javafx.scene.control.Label titulo =
new javafx.scene.control.Label("Resumen Rápido");
titulo.getStyleClass().add("card-title");
card.getChildren().add(titulo);
String[][] items = {
{"Ingresos hoy", "$3,420"},
{"Tickets abiertos", "12"},
{"Tiempo promedio", "2.4 min"},
{"Satisfacción", "94%"},
{"Tasa de error", "0.3%"}
};
for (String[] item : items) {
HBox fila = new HBox();
fila.getStyleClass().add("summary-row");
javafx.scene.control.Label label =
new javafx.scene.control.Label(item[0]);
label.getStyleClass().add("summary-label");
javafx.scene.layout.Region spacer =
new javafx.scene.layout.Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
javafx.scene.control.Label valor =
new javafx.scene.control.Label(item[1]);
valor.getStyleClass().add("summary-value");
fila.getChildren().addAll(label, spacer, valor);
card.getChildren().add(fila);
}
return card;
}
}
Paso 8: dashboard.css — Los Estilos
Este es todo el CSS del dashboard. Es extenso pero organizado por secciones:
Variables y Root
.root-container {
-fx-font-family: 'Segoe UI', system-ui, sans-serif;
-fx-font-size: 14;
-color-bg: #0f172a;
-color-surface: #1e293b;
-color-border: #334155;
-color-text: #f1f5f9;
-color-text-secondary: #94a3b8;
-color-text-muted: #64748b;
-color-primary: #3b82f6;
-color-success: #10b981;
-color-warning: #f59e0b;
-color-danger: #ef4444;
-fx-background-color: -color-bg;
}
Sidebar
.sidebar {
-fx-background-color: #0c1222;
-fx-padding: 20 16;
-fx-spacing: 8;
-fx-border-width: 0 1 0 0;
-fx-border-color: -color-border;
}
.sidebar-logo {
-fx-text-fill: white;
-fx-font-size: 20;
-fx-font-weight: bold;
-fx-padding: 0 0 20 8;
}
.menu-item {
-fx-padding: 10 12;
-fx-background-radius: 8;
-fx-cursor: hand;
}
.menu-item:hover {
-fx-background-color: -color-surface;
}
.menu-item-active {
-fx-background-color: -color-primary;
}
.menu-item-active .menu-text {
-fx-text-fill: white;
-fx-font-weight: bold;
}
Stat Cards
.stat-card {
-fx-padding: 20;
-fx-background-color: -color-surface;
-fx-background-radius: 12;
-fx-border-color: -color-border;
-fx-border-radius: 12;
-fx-border-width: 1;
}
.stat-value {
-fx-text-fill: -color-text;
-fx-font-size: 28;
-fx-font-weight: bold;
}
.stat-icon {
-fx-font-size: 24;
-fx-padding: 8;
-fx-background-radius: 10;
-fx-min-width: 44;
-fx-min-height: 44;
-fx-alignment: center;
}
/* Colores por variante */
.stat-blue .stat-icon {
-fx-background-color: rgba(59, 130, 246, 0.15);
}
.stat-green .stat-icon {
-fx-background-color: rgba(16, 185, 129, 0.15);
}
.stat-amber .stat-icon {
-fx-background-color: rgba(245, 158, 11, 0.15);
}
.stat-violet .stat-icon {
-fx-background-color: rgba(139, 92, 246, 0.15);
}
.stat-trend-up {
-fx-text-fill: -color-success;
-fx-font-size: 12;
}
.stat-trend-down {
-fx-text-fill: -color-danger;
-fx-font-size: 12;
}
Tabla
.table-header {
-fx-background-color: rgba(30, 41, 59, 0.6);
}
.table-header-text {
-fx-text-fill: -color-text-muted;
-fx-font-size: 12;
-fx-font-weight: bold;
}
.table-row {
-fx-border-width: 0 0 1 0;
-fx-border-color: rgba(51, 65, 85, 0.5);
}
.table-row:hover {
-fx-background-color: rgba(51, 65, 85, 0.3);
}
.table-cell-primary {
-fx-text-fill: -color-text;
-fx-font-weight: 600;
}
Gráfico de Barras
.chart-bar {
-fx-fill: -color-primary;
}
.chart-bar:hover {
-fx-fill: #2563eb;
}
Pro tip: En el archivo ZIP encontrarás el CSS completo con todas las secciones: sidebar profile, topbar, scrollbar personalizado, cards genéricos y más.
Paso 9: Ejecutar el Dashboard
- Descomprime el archivo ZIP descargado
- Abre
run.baty configura la ruta de tu JavaFX SDK:
set JAVAFX_PATH=E:\Java\javafx-sdk-24.0.1\lib
- Haz doble clic en
run.bat

Conceptos Clave Aprendidos
| Concepto | Dónde se usa |
|---|---|
BorderPane para layouts complejos | App.java (sidebar + contenido) |
VBox.setVgrow para espaciadores | Sidebar (perfil al fondo) |
HBox.setHgrow para distribución | DashboardView (stat cards) |
Timeline para animaciones | StatCard (contador animado) |
ScrollPane para contenido largo | DashboardView |
Rectangle para gráficos custom | ChartPanel (barras) |
| CSS variables para temas | dashboard.css (colores) |
Pseudo-clases :hover | Botones, filas de tabla |
Extendiendo el Dashboard
Algunas ideas para mejorar el dashboard:
- Datos reales: Conecta una base de datos con JDBC para mostrar datos dinámicos
- Gráficos avanzados: Usa las clases
Chartde JavaFX (LineChart,PieChart) - Modo claro: Agrega un toggle que cambie las variables CSS
- Navegación real: Haz que cada menú del sidebar cambie el contenido central
- Responsive: Usa listeners en
widthProperty()para adaptar el layout
Siguiente Paso
¡Tu dashboard está listo! En los próximos artículos exploraremos cómo conectar datos reales, crear gráficos interactivos y agregar notificaciones toast animadas.