Cuando Vaadin Implementa tu Add-on: Qué Hicimos con Toggle Button

En mayo de 2026 publicamos la versión 1.0 de Toggle Button, un toggle para Vaadin Flow. Unos meses después, Vaadin 25.3 incorporó Switch, un componente del core que hace casi lo mismo.
Si mantienes add-ons, tarde o temprano te pasa: el framework se pone al día y lanza algo muy parecido a lo que construiste. ¿Y ahora qué? ¿Deprecas tu add-on, lo dejas como está o lo cambias? Aquí te contamos qué comparamos, qué opciones pusimos sobre la mesa, qué decidimos para Toggle Button y por qué.
Por qué creamos Toggle Button
Cuando empezamos Toggle Button, no sabíamos que Switch estaba en camino. Ya existían otras opciones, pero nos pareció que valía la pena construir la nuestra por sus funcionalidades y su implementación:
- Etiquetas a ambos lados del toggle (“Off” / “On”, “Monthly” / “Yearly”), con un
ItemLabelGenerator<Boolean>opcional. - Íconos a ambos lados, por fuera o junto al switch.
- Resaltado de etiquetas: la etiqueta del lado activo toma el color de la variante y la otra se atenúa.
- Variantes de color (
PRIMARY,SUCCESS,WARNING,ERROR,CONTRAST) y de tamaño (SMALL,MEDIUM,LARGE). - Swipe en pantallas táctiles, y una variante
LONGSWIPEcon un recorrido más ancho para móviles. - Una API fluida.
ToggleButton toggle = new ToggleButton()
.setLeftLabel("Dark")
.setRightLabel("Light")
.setLeftIcon(new Icon(VaadinIcon.MOON))
.setRightIcon(new Icon(VaadinIcon.SUN_O))
.withHighlightLabel();
toggle.addThemeVariants(ToggleButtonVariant.SUCCESS, ToggleButtonVariant.LARGE);
Y entonces llegó Switch
Vaadin 25.3 presentó Switch, un campo para activar o desactivar un único ajuste. Lo comparamos con Toggle Button 1.x funcionalidad por funcionalidad, y no nos quedamos solo con la documentación: también revisamos el código fuente del web component y de la clase de Flow.
Switch del core (25.3) | ToggleButton 1.x | |
|---|---|---|
| Etiqueta del campo | Sí | Sí |
| Etiquetas a ambos lados | No | Sí |
| Íconos a ambos lados | No | Sí |
| Resaltado de la etiqueta activa | No | Sí |
| Variantes de color | No (propiedades CSS) | 5 variantes |
| Variantes de tamaño | Small (solo Aura) | Small, medium, large |
| Swipe táctil | No | Sí |
| Teclado (foco, Espacio para cambiar) | Sí | No |
| Se anuncia como switch con su estado | Sí (role="switch") | No |
| Texto de ayuda | Sí | No |
| Obligatorio y validación | Sí | No |
| Colores forzados (alto contraste) | Sí | No |
La comparación fue clara para ambos lados. Switch estaba muy por delante en accesibilidad e integración con formularios. Seamos honestos: Toggle Button 1.x no podía recibir el foco ni cambiarse con el teclado, y los lectores de pantalla no lo anunciaban como switch ni leían su estado. En cambio, ninguna de nuestras funcionalidades visuales tenía un equivalente en el core.
¿Deprecar, mantener el nuestro o construir encima?
Lo discutimos en equipo en una reunión, con tres opciones sobre la mesa:
- Deprecar Toggle Button y recomendar Switch. Pero el add-on hace cosas que el core no hace, como las variantes, las etiquetas e íconos laterales y el swipe. Deprecarlo dejaría a esos usuarios sin alternativa.
- Mantener nuestro propio web component y agregarle nosotros la accesibilidad que faltaba:
role="switch",aria-checked,tabindex, manejo de teclado, colores forzados. Es posible, pero implica mantener para siempre algo que Vaadin ya mantiene, y probablemente mejor. - Construir encima de Switch: dejar que el core se encargue del switch en sí y quedarnos solo con lo que nos diferencia.
Elegimos la tercera: migrar, no deprecar. El objetivo que dejamos escrito en el issue fue una nueva major version que conserve la API mientras sea posible, delegue en Switch y agregue encima lo que falte.
Con Switch en el core, el add-on solo tiene sentido por lo que el core no ofrece. Ahí es donde tiene que ir nuestro esfuerzo.
¿Cómo se construye encima de un componente del core?
La reunión definió qué hacer. Cómo construir encima de Switch seguía abierto, y lo resolvimos en el issue revisando su código fuente: el web component, la clase de Flow, sus estilos de Lumo y Aura, y la propia especificación de Vaadin para el componente. “Construir encima de Switch” puede significar varias cosas. Analizamos tres y elegimos la composición, la única que se sostuvo. No hicimos prototipos de las otras dos: leer el código alcanzó para descartarlas.
A. Extender Switch en Java. Era la primera idea obvia, pero no funciona. vaadin-switch no tiene un slot por defecto, así que los hijos adicionales no se renderizan y las etiquetas e íconos laterales se vuelven imposibles. Además, Switch ya implementa HasThemeVariant<SwitchVariant>, así que nuestra clase no puede implementar también HasThemeVariant<ToggleButtonVariant>. Y los eventos de cambio de valor pasarían a ser ComponentValueChangeEvent<Switch, Boolean>, un breaking change para cualquier código que use el tipo del evento.
B. Extender el web component en JavaScript y reemplazar su template. Esto implica copiar un template interno que puede cambiar en cualquier versión menor. Los estilos de Aura apuntan al tag vaadin-switch, así que no se aplicarían a un tag propio, y la integración con Lumo depende de API interna. Demasiado frágil.
C. Composición (nuestra elección). Un wrapper liviano, fc-toggle-button, que contiene un vaadin-switch real:
[ícono] [etiqueta] <vaadin-switch> [etiqueta] [ícono]Solo usa API pública, y Lumo y Aura se aplican sin cambios, así que fue el enfoque que elegimos. Lo propusimos en el issue y lo implementamos en un pull request para que el equipo lo revisara. Del lado de Java, ToggleButton extiende AbstractField<ToggleButton, Boolean> y contiene un Switch interno. El valor, el modo de solo lectura, el estado deshabilitado, el foco y el tooltip se delegan a ese Switch.
Donde más ayuda la API del core es en la accesibilidad. Switch permite referenciar elementos externos para su nombre y su descripción accesibles, así que el switch interno los toma de las etiquetas y del texto de ayuda que renderiza nuestro wrapper:
private void updateAccessibleName() {
if (!isBlank(ariaLabelledBy)) {
toggle.setAriaLabel(null);
toggle.setAriaLabelledBy(ariaLabelledBy);
return;
}
if (!isBlank(ariaLabel)) {
toggle.setAriaLabelledBy((String) null);
toggle.setAriaLabel(ariaLabel);
return;
}
toggle.setAriaLabel(null);
toggle.setAriaLabelledBy(label.isVisible() ? visibleIds(label) : visibleIds(leftLabel, rightLabel));
}Aquí toggle es el Switch interno. El nombre accesible sale de un aria-labelledby explícito, después de la etiqueta ARIA, después de la etiqueta del campo y, por último, de las etiquetas laterales. Las etiquetas se referencian por id, así que se mantienen sincronizadas cuando cambia su texto. El texto de ayuda y el mensaje de error se vinculan de la misma forma con aria-describedby.
El resultado: Toggle Button 2.0
Toggle Button 2.0 requiere Vaadin 25.3+ y Java 21. La versión 1.x sigue en mantenimiento para Vaadin 24 y 25.0–25.2.
Lo que ganamos, casi sin esfuerzo:
- Los lectores de pantalla lo anuncian como switch, con su estado activado o desactivado.
- Recibe el foco y se cambia con la tecla Espacio.
- Texto de ayuda, indicador de obligatorio, mensajes de error y validación con
Binder. - Soporte para RTL, colores forzados y movimiento reducido.
- Funciona tanto con Lumo como con Aura.
ToggleButton confirm = new ToggleButton("I confirm the trip details are correct")
.setLeftLabel("No")
.setRightLabel("Yes");
binder.forField(confirm)
.asRequired("You must confirm the trip details to continue")
.bind(Booking::isConfirmed, Booking::setConfirmed);
Binder, texto de ayuda, validación de obligatorio y swipe. Pruébala en vivo.Lo que mantuvimos: la API fluida y el tipo de evento de cambio de valor (ComponentValueChangeEvent<ToggleButton, Boolean>). La composición lo hizo posible: la mayor parte del código de la 1.x compila y funciona sin cambios.
Lo que cambió:
ToggleButtonya no implementaHasComponents. Ahora el componente administra sus propios hijos (el switch interno, las etiquetas, el texto de ayuda y el mensaje de error), y unadd()oremove()público permitiría romper esa estructura. Los íconos se configuran consetLeftIconysetRightIcon.- Sin variante de tamaño, el componente usa el tamaño nativo del switch del tema. Para mantener el aspecto de la 1.x, agrega
MEDIUM. - El tooltip se asocia al switch, no a las etiquetas laterales. Así se abre al recibir el foco con el teclado y los lectores de pantalla lo anuncian.
- Al hacer swipe, el marcador ya no sigue al dedo. El estado cambia cuando el gesto supera la mitad del recorrido. Hacer que el marcador siga al dedo implicaría tocar estilos inline dentro del shadow root de Switch, que es justamente el tipo de fragilidad que queríamos evitar.
Una sorpresa en el camino: desde Flow 24.9.2, Binder agrega a cualquier campo inválido un theme con el nombre del nivel de validación (error, warning…), y lo quita cuando el campo vuelve a ser válido. Nuestras variantes ERROR y WARNING escribían esos mismos nombres en el atributo theme, así que un Toggle Button vinculado con Binder perdía su color apenas se validaba. En la 2.0, las variantes escriben color-error, color-warning, etc. Las constantes de Java no cambiaron, así que solo se ve afectado el CSS personalizado que apunta al atributo.
La lista completa de breaking changes está en la sección Migrating from 1.x del README.

¿Switch, Toggle Button o radio button group?
Tener los dos componentes plantea una pregunta razonable: ¿cuál conviene usar? La propia guía de Vaadin ayuda a responderla.
- Usa Switch del core para un único ajuste que está activado o desactivado, como “Notificaciones” o “Guardado automático”. Si no necesitas nada más, no necesitas un add-on.
- Usa Toggle Button cuando necesites lo que Switch no ofrece: etiquetas o íconos a ambos lados, variantes de color o tamaño, resaltado de etiquetas o un recorrido más ancho para pantallas táctiles.
- Usa un radio button group de dos opciones cuando los dos valores son opciones equivalentes y no un “activado” y “desactivado”, como “Imperial” y “Métrico”. Vaadin lo recomienda en lugar de un switch.
Las etiquetas a ambos lados como “Mensual” / “Anual” se acercan a ese último caso, y aun así son un patrón muy común en aplicaciones reales. Si las usas, ten en cuenta que los lectores de pantalla solo anuncian “activado” o “desactivado”. Cuando las etiquetas laterales tienen significado propio, inclúyelo también en la etiqueta del campo o en la etiqueta ARIA. El README del add-on explica cómo hacerlo.
¿Y si Switch sigue creciendo?
Esta decisión refleja cómo es Switch hoy, y Switch va a seguir evolucionando. Algo de lo que hoy diferencia a Toggle Button podría llegar al core más adelante. El swipe es un buen ejemplo: la especificación de Vaadin deja el arrastre y el swipe fuera de la primera versión de Switch (vaadin/web-components#893), pero aclara que se podría agregar después sin breaking changes.
Construir encima de Switch nos deja bien posicionados para eso. Cada mejora que Vaadin hace en Switch llega a Toggle Button sin trabajo de nuestra parte. Cuando una funcionalidad nuestra llegue al core, podemos quitar nuestra versión y delegarla, como ya hicimos con la accesibilidad y la validación.
Y si algún día Switch cubre todo lo que ofrece Toggle Button, deprecar el add-on será el paso natural, no una pérdida: significará que el core creció hasta cubrir esa necesidad.
Lo que nos llevamos
- Primero el core, el add-on cuando el core no alcanza. Un componente del core siempre debería ser la primera opción. Pero cuando no cubre todo lo que necesitas, contar con una alternativa es una razón para mantenerla viva.
- Que el core lance un equivalente es una buena noticia. Confirma que la necesidad era real y te da una base sólida que no tienes que mantener.
- Compara con el código fuente, no solo con la documentación. La documentación nos dijo qué hace Switch. El código nos dijo cómo podíamos construir encima y por qué extenderlo no iba a funcionar.
- Delega la base, quédate con la diferencia. Soltamos lo que el core hace mejor (el switch, la accesibilidad, los formularios) y nos quedamos con lo que solo ofrecemos nosotros (lo visual).
- Prefiere la composición a la herencia al construir sobre un componente del core. Te mantiene en la API pública y protege a tus usuarios de breaking changes.
- Usa una major version y mantén viva la anterior. Quienes usan versiones anteriores de Vaadin siguen con la 1.x, y los proyectos nuevos usan la 2.0.
Pruébalo
Toggle Button 2.0.0 está disponible en el Vaadin Directory y en Maven Central:
<dependency>
<groupId>com.flowingcode.vaadin.addons</groupId>
<artifactId>toggle-button-addon</artifactId>
<version>2.0.0</version>
</dependency>Mira la demo online y el código fuente.
¿Te pasó algo parecido con alguno de tus componentes? ¿Lo deprecaste, lo mantuviste o lo reconstruiste? Nos encantaría saber cómo lo resolviste. ¡Cuéntanos en los comentarios!
Gracias por leernos ¡and let’s keep the code flowing!
¡Sumate a la conversación!