Botones Neumórficos (Neumorphism) con puro CSS de JavaFX
Aprende a usar avanzados efectos de sombra encadenados (DropShadow e InnerShadow) para crear interfaces suaves donde los botones parecen estar incrustados en su fondo.
JJ Arroyo
2 de marzo de 2026 • 6 min de lectura

El Neumorfismo (o Soft UI) es un estilo de diseño visual donde los elementos de la interfaz parecen estar creados con el mismo material del fondo. En lugar de estar completamente elevados o dibujados como cuadros planos, parecen "extruidos" de la misma superficie, usando sombras claras en un borde y sombras oscuras en el borde opuesto.
A diferencia del estilo plano tradicional, este diseño transmite una sensación física muy agradable y limpia, ideal para paneles de control médicos, aplicaciones musicales o herramientas del hogar inteligente.
En este artículo, exploraremos cómo conseguir este look en botones JavaFX utilizando puro CSS mediante cascadas de efectos -fx-effect, sin escribir lógica extraña de código Java.
El Concepto (Versión Tailwind CSS)
Primero, entendamos las matemáticas visuales detrás del Neumorfismo. En código web, es normal ver combinaciones intensas de box-shadow múltiple. Abajo tienes unos botones neumórficos vivos constuidos en TailwindCSS, interactúa con ellos:
Normal
Presionado
Píldora
La regla de oro del Neumorfismo es: El color de fondo del elemento debe ser IDÉNTICO al del contenedor padre. En el ejemplo usamos #e0e5ec (Un gris azulado suave).
Luego, las sombras:
- Sombra Clara (Superior Izquierda): Brinda luz. Simula de dónde viene el sol.
- Sombra Oscura (Inferior Derecha): Crea volumen y profundidad.
Cuando el botón se presiona (estado active), las sombras exteriores desaparecen y se convierten en Sombras Interiores (inset), haciendo que parezca que presenciaste físicamente el plástico hacia dentro de la carcasa.
Pasándolo al CSS de JavaFX
JavaFX tiene las funciones dropshadow y innershadow. El gran reto aquí es que JavaFX CSS no permite empaquetar múltiples sombras de forma separada por comas en -fx-effect de la misma manera sencilla que CSS3.
Para aplicar varias sombras a un mismo nodo, tienes que encadenarlas. Esto resulta en un string de CSS muy largo pero asombrosamente funcional.
1. Preparando la paleta
Define la base de la ventana. Es obligatorio que el contenedor raíz comparta este color:
.root {
-color-bg: #e0e5ec;
-fx-background-color: -color-bg;
}
2. El Botón Neumórfico Estándar (Elevado)
Aquí vamos a encadenar dos envoltorios dropshadow(). La sintaxis es: dropshadow( blur-type , color , radius , spread , offsetX , offsetY ). Para encadenarlos solo abres otro dentro del primero.
/* button-neumorphic.css */
.btn-neumorphic {
-fx-background-color: #e0e5ec;
-fx-background-radius: 12px;
-fx-text-fill: #6c7a92;
-fx-font-weight: bold;
-fx-font-size: 16px;
-fx-padding: 15px 30px;
/* El truco: Una sombra oscura abajo-derecha, ENVUELVE una sombra blanca arriba-izquierda */
-fx-effect:
dropshadow(three-pass-box, rgba(163, 177, 198, 0.6), 15, 0, 8, 8),
dropshadow(three-pass-box, rgba(255, 255, 255, 0.8), 15, 0, -8, -8);
-fx-cursor: hand;
}
/* Hover: Darle un poquito de tinte azul al texto */
.btn-neumorphic:hover {
-fx-text-fill: #3b82f6;
}
3. El Switch de Presionado (Estado Pressed o Inner)
Para simular estar hundido, usaremos innershadow. Y a diferencia del web box-shadow nativo de CSS, encadenar innershadows en JavaFX CSS usa la misma técnica de anidación. A menudo, usar Java puro y los objetos InnerShadow encadenados mediante la propiedad java.scene.effect.Effect.setInput() da resultados más predecibles si ves que el CSS se satura.
Pero si quieres todo CSS:
.btn-neumorphic:pressed {
/* Las dimensiones bajan poco, y la sombra va hacia el interior */
-fx-effect:
innershadow(three-pass-box, rgba(163, 177, 198, 0.7), 10, 0, 5, 5),
innershadow(three-pass-box, rgba(255, 255, 255, 0.8), 10, 0, -5, -5);
}
Creando el Componente y Vista en Java
Para mantener el código puro y poder aplicarlo directo a cualquier aplicación instalada (como el App de test), aquí tienes la implementación en vivo que pinta una escena directamente con objetos de configuración en Java:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.InnerShadow;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
public class NeumorphismDemo extends Application {
@Override
public void start(Stage primaryStage) {
// Color Base estricto Neumórfico
Color bgColor = Color.web("#e0e5ec");
VBox root = new VBox(40);
root.setAlignment(Pos.CENTER);
root.setStyle("-fx-background-color: #e0e5ec;");
HBox buttonContainer = new HBox(40);
buttonContainer.setAlignment(Pos.CENTER);
// Sombras Exteriores (Elevado)
DropShadow dropDark = new DropShadow(15, 8, 8, Color.web("rgba(163,177,198,0.6)"));
DropShadow dropLight = new DropShadow(15, -8, -8, Color.web("rgba(255,255,255,0.8)"));
dropDark.setInput(dropLight); // ¡Encadenamiento en Java!
// Sombras Interiores (Hundido/Presionado)
InnerShadow innerDark = new InnerShadow(10, 5, 5, Color.web("rgba(163,177,198,0.7)"));
InnerShadow innerLight = new InnerShadow(10, -5, -5, Color.web("rgba(255,255,255,0.8)"));
innerDark.setInput(innerLight); // ¡Encadenamiento en Java!
// --- Botón 1: Normal (Píldora) ---
Button btnFlat = new Button("Confirmar Pago");
btnFlat.setFont(Font.font("System", FontWeight.BOLD, 16));
btnFlat.setStyle(
"-fx-background-color: #e0e5ec;" +
"-fx-text-fill: #6c7a92;" +
"-fx-background-radius: 30;" +
"-fx-padding: 20 40;"
);
btnFlat.setEffect(dropDark);
// Efecto Pressed hecho programáticamente para evitar fallos de parser CSS
btnFlat.setOnMousePressed(e -> btnFlat.setEffect(innerDark));
btnFlat.setOnMouseReleased(e -> btnFlat.setEffect(dropDark));
// --- Botón 2: Botón Redondo (Pre-hundido) ---
Button btnPressed = new Button("⏸");
btnPressed.setFont(Font.font("System", FontWeight.BOLD, 24));
btnPressed.setStyle(
"-fx-background-color: #e0e5ec;" +
"-fx-text-fill: #3b82f6;" + // Azul resaltado
"-fx-background-radius: 50em;" + // Redondo perfecto
"-fx-min-width: 80;" +
"-fx-min-height: 80;"
);
// Este siempre parece hundido (ideal para controles encendidos o toggles)
btnPressed.setEffect(innerDark);
buttonContainer.getChildren().addAll(btnFlat, btnPressed);
root.getChildren().add(buttonContainer);
Scene scene = new Scene(root, 600, 400);
primaryStage.setTitle("JavaFX Neumorphism");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
El Superpoder de setInput()
Como habrás visto en el código Java anterior, evité poner las sombras anidadas complejas en un solo String de setStyle, porque JavaFX a veces no renderiza bien cadenas que requieran múltiple anidamiento profundo con comas en CSS.
En su lugar, usar dropDark.setInput(dropLight) le dice explícitamente al Scene Builder Engine: Primero dibuja la luz, y la imagen resultante envíasela a la sombra oscura, renderizando ambas perfectamente alineadas. ¡Este truco programático evitará horas de dolores de cabeza!
Conclusión
El Neumorfismo puede ser controvertido por cuestiones de contraste y accesibilidad, pero para interfaces cerradas sin mucho recargo de datos (Paneles industriales, calculadoras o players de audio) queda maravillosamente elegante usando tan solo las herramientas nativas de JavaFX.