Sistema de Login Multi-Tema Animado en JavaFX
Sube el nivel de tus aplicaciones JavaFX con una pantalla de Login profesional. Incluye animaciones de error (Shake), transiciones suaves y cambio de tema dinámico en tiempo real.
JJ Arroyo
3 de marzo de 2026 • 9 min de lectura

Una pantalla de autenticación no debería ser solo dos cajas de texto aburridas. Es la primera impresión que tienen los usuarios de tu software. En este proyecto construiremos un Sistema de Login Animado robusto y visualmente deslumbrante usando JavaFX.
¿Qué vamos a implementar?
- Animación de Error ("Shake"): Cuando el usuario ingrese mal la contraseña, la ventana vibrará igual que lo hace macOS o Windows.
- Fade Transitions: Un fundido suave para transicionar a la pantalla "Dashboard" una vez que el login sea exitoso.
- Persistencia (Recordar Usuario): Usaremos
java.util.prefs.Preferencespara guardar el nombre de usuario localmente. - Temas Dinámicos: Cambiaremos el archivo
.cssen vivo desde unComboBox(Light SaaS, Dark Glass y Neumorphism).
1. La Interfaz (FXML) y el Selector de Temas
Nuestra vista principal (LoginView.fxml) utiliza un StackPane como base. Esto nos permite poner la tarjeta de Login centrada y añadir un menú flotante en la esquina superior derecha para el selector de temas.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.collections.FXCollections?>
<?import java.lang.String?>
<StackPane fx:id="rootPane" styleClass="main-bg" stylesheets="@../css/style-light.css" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.login_animado.controller.LoginController">
<!-- Selector de Tema (Arriba a la derecha) -->
<HBox alignment="TOP_RIGHT" spacing="10" StackPane.alignment="TOP_RIGHT">
<padding><Insets top="20" right="20" /></padding>
<Label text="Estilo:" styleClass="theme-label" />
<ComboBox fx:id="themeComboBox" styleClass="theme-combo" onAction="#handleThemeSwitch">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Light SaaS" />
<String fx:value="Dark Glass" />
<String fx:value="Neumorphism" />
</FXCollections>
</items>
<value><String fx:value="Light SaaS" /></value>
</ComboBox>
</HBox>
<!-- Tarjeta de Login -->
<VBox fx:id="loginCard" styleClass="login-card" alignment="CENTER" spacing="25" maxWidth="400" maxHeight="500">
<StackPane.margin><Insets top="40" /></StackPane.margin>
<padding><Insets top="40" right="40" bottom="40" left="40" /></padding>
<VBox alignment="CENTER" spacing="10">
<Circle radius="35" styleClass="logo-circle" />
<Label text="Bienvenido" styleClass="title-label" />
<Label text="Ingresa a tu cuenta para continuar" styleClass="subtitle-label" />
</VBox>
<VBox spacing="15" VBox.vgrow="ALWAYS">
<VBox spacing="5">
<Label text="Usuario" styleClass="input-label" />
<TextField fx:id="txtUser" promptText="ej. admin" styleClass="input-field" />
</VBox>
<VBox spacing="5">
<Label text="Contraseña" styleClass="input-label" />
<PasswordField fx:id="txtPassword" promptText="••••••••" styleClass="input-field" />
</VBox>
<CheckBox fx:id="chkRememberMode" text="Recordar usuario" styleClass="check-box" />
</VBox>
<Label fx:id="lblError" text="Error de credenciales" styleClass="error-label" visible="false" managed="false" />
<Button text="Iniciar Sesión" onAction="#handleLogin" styleClass="btn-primary" maxWidth="Infinity" />
</VBox>
</StackPane>
2. Los Múltiples Temas (CSS)
La magia de poder cambiar de tema en tiempo real radica en separar completamente la lógica de los estilos. Hemos creado tres archivos en src/main/resources/css/.
[!TIP] Si te interesa entender a fondo cómo funcionan las sombras del tema Neumorphism, revisa nuestro artículo dedicado a los Botones Neumórficos en JavaFX.
Tema 1: Light SaaS
Un diseño ultra-limpio, fondos blancos, bordes sutiles y botones azules prominentes. Perfecto para aplicaciones B2B.

Tema 2: Dark Glass
Elegante gradiente oscuro con transparencias (rgba) que interactúan entre la ventana flotante y el fondo oscuro.

Tema 3: Neumorphism
Simula plástico moldeado por inyección. Para este CSS utilizamos combinaciones complejas de dropshadow e innershadow.

3. El Controlador: Animación y Guardado Local
Aquí es donde atamos todo:
- Cambio de Tema: Limpiamos la lista de estilos (
rootPane.getStylesheets().clear()) y cargamos el nuevo CSS correspondiente al valor delComboBox. - Animación "Shake": Instanciamos una
TranslateTransitionde apenas 50 milisegundos que mueve la tarjeta en el eje X (setByX(10f)), repetida 6 veces. - Animación Fade: Usamos
FadeTransitionen el nodo raíz para hacerlo desaparecer (1.0a0.0), y en el eventosetOnFinished(...)cargamos el FXML del Dashboard y lo hacemos aparecer (0.0a1.0). - Java Preferences:
java.util.prefs.Preferencessalva pequeños datos en el registro (Windows) o archivos ocultos (Mac/Linux) automáticamente sin lidiar con.txto bases de datos SQLite para pequeñeces como "Recordar el usuario".
package com.login_animado.controller;
import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import java.io.IOException;
import java.util.prefs.Preferences;
public class LoginController {
@FXML private StackPane rootPane;
@FXML private VBox loginCard;
@FXML private ComboBox<String> themeComboBox;
@FXML private TextField txtUser;
@FXML private PasswordField txtPassword;
@FXML private CheckBox chkRememberMode;
@FXML private Label lblError;
// Constantes para Local Storage de Java
private Preferences prefs;
private static final String PREF_REMEMBER = "remember_user";
private static final String PREF_USER = "saved_user";
@FXML
public void initialize() {
prefs = Preferences.userNodeForPackage(LoginController.class);
// Cargar preferencias guardadas la última vez
boolean remember = prefs.getBoolean(PREF_REMEMBER, false);
chkRememberMode.setSelected(remember);
if (remember) {
String savedUser = prefs.get(PREF_USER, "");
txtUser.setText(savedUser);
// Salta el foco a la contraseña ya que el usuario está lleno
Platform.runLater(() -> txtPassword.requestFocus());
}
}
@FXML
protected void handleThemeSwitch(ActionEvent event) {
String theme = themeComboBox.getValue();
rootPane.getStylesheets().clear();
String cssPath;
switch (theme) {
case "Dark Glass":
cssPath = "/css/style-dark.css";
break;
case "Neumorphism":
cssPath = "/css/style-neu.css";
break;
case "Light SaaS":
default:
cssPath = "/css/style-light.css";
break;
}
rootPane.getStylesheets().add(getClass().getResource(cssPath).toExternalForm());
}
@FXML
protected void handleLogin(ActionEvent event) {
String user = txtUser.getText();
String pass = txtPassword.getText();
lblError.setVisible(false);
lblError.setManaged(false);
// Validación simple de prueba
if ("admin".equals(user) && "admin".equals(pass)) {
handleSuccessfulLogin();
} else {
// Animación Shake de Error
showError("Usuario o contraseña incorrectos");
}
}
private void showError(String msg) {
lblError.setText(msg);
lblError.setVisible(true);
lblError.setManaged(true);
shakeAnimation(loginCard);
}
private void shakeAnimation(javafx.scene.Node node) {
TranslateTransition tt = new TranslateTransition(Duration.millis(50), node);
tt.setFromX(0f);
tt.setByX(10f);
tt.setCycleCount(6);
tt.setAutoReverse(true);
tt.playFromStart();
}
private void handleSuccessfulLogin() {
// Guardado de Preferencia si está marcado
if (chkRememberMode.isSelected()) {
prefs.putBoolean(PREF_REMEMBER, true);
prefs.put(PREF_USER, txtUser.getText());
} else {
prefs.putBoolean(PREF_REMEMBER, false);
prefs.remove(PREF_USER);
}
// Animación FadeOut de salida
FadeTransition ftOut = new FadeTransition(Duration.millis(300), rootPane);
ftOut.setFromValue(1.0);
ftOut.setToValue(0.0);
ftOut.setOnFinished(e -> loadDashboard());
ftOut.play();
}
private void loadDashboard() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/DashboardView.fxml"));
Parent dashboardRoot = loader.load();
// Pasa el tema actual a la nueva vista del dashboard para continuidad
dashboardRoot.getStylesheets().addAll(rootPane.getStylesheets());
Scene scene = rootPane.getScene();
scene.setRoot(dashboardRoot);
// Animación FadeIn de entrada
FadeTransition ftIn = new FadeTransition(Duration.millis(400), dashboardRoot);
ftIn.setFromValue(0.0);
ftIn.setToValue(1.0);
ftIn.play();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Descarga el Código Completo
Lleva tus habilidades a la práctica. Hemos empaquetado todo el proyecto junto con los tres archivos CSS completos y las vistas listas para ejecutar.
Ahora ya sabes cómo dar una experiencia profesional combinando FXML, transiciones suaves y temas CSS dinámicos directos en el corazón de JavaFX.