Referencia¶
Referencia completa de la API pública de Aurora. Para un recorrido paso a paso, empieza por la guía. Qué se considera público y cómo cambia entre versiones lo explica la política de estabilidad.
- App
- Window (ventanas adicionales)
- Diálogos nativos
- Bandeja del sistema
- Notificaciones
- Menús nativos
- Signal
- component
- Pilot (tests de interacción)
- Inspector y profiler de frames
- Hot reload
- Primitivas
- Teclado y foco
- Accesibilidad
- Componentes
- Animaciones
- Propiedades de estilo
- Temas y tokens
- Formatos de valores
- Empaquetado:
aurora build - Avisos de obsolescencia
App¶
App(view, *, title="Aurora", size=(960, 640), theme=MIDNIGHT, inspector=None, reload=None, motion=None)
| Parámetro | Tipo | Descripción |
|---|---|---|
view |
Callable[[], Element] |
Función sin argumentos que devuelve el árbol de la interfaz. Se llama de nuevo tras cada evento. |
title |
str |
Título de la ventana. |
size |
tuple[float, float] |
Tamaño inicial en píxeles lógicos (independientes del DPI). |
theme |
Theme \| Callable[[], Theme] |
Tema fijo, o un Signal[Theme] para cambiarlo en caliente. |
menu |
Sequence[dict] \| None |
La barra de menús nativa (Windows y macOS), descrita con menus: App(view, menu=menus.bar(...)). En Linux no hay barra nativa. |
app_id |
str \| None |
(Windows) El AppUserModelID: la identidad con la que Windows firma las notificaciones y agrupa la barra de tareas. En una app empaquetada se deriva del título si no la indicas. |
inspector |
bool \| None |
Permite abrir con F12 el inspector y profiler de frames. Con None (por defecto) lo decide la variable de entorno AURORA_INSPECTOR (1, true, yes u on lo activan). |
reload |
bool \| None |
Activa el hot reload en run(). Con None (por defecto) lo decide la variable de entorno AURORA_RELOAD, como inspector. |
motion |
bool \| None |
Con False, los componentes no animan por defecto (switchs, casillas, pestañas y menús): sus cambios son inmediatos. Con None (por defecto) lo decide AURORA_MOTION (0, false, no u off lo desactivan). |
| Método | Descripción |
|---|---|
run(*, backend="auto", keep_alive=None) |
Abre las ventanas y bloquea hasta que se cierren todas. backend elige dónde se pinta: "gpu" (Vulkan en Windows y Linux, Metal en macOS), "cpu" o "auto" (GPU si se puede iniciar; si no, CPU). Con "auto", la variable de entorno AURORA_BACKEND (gpu/cpu) lo fuerza. "gpu" sin GPU disponible lanza RuntimeError; un valor desconocido, ValueError. keep_alive=True mantiene la app en marcha aunque no queden ventanas (para la bandeja del sistema) hasta que se llame a quit(). |
window(view, *, title="Aurora", size=None, resizable=True, on_close=None) |
Crea una ventana adicional con su propia vista (ver Window). size por defecto es el de la ventana principal. |
windows() |
Las ventanas creadas con window(...), en orden de creación (sin la principal). |
main |
La ventana principal, como un Window (ver abajo). |
notify(title, body="", *, icon=None, on_click=None) |
Muestra una notificación del sistema. |
register_app_id(name=None) |
(Windows) Registra la identidad de la app con un acceso directo del menú Inicio (ver Notificaciones). |
quit() |
Cierra todas las ventanas y termina la app. Seguro desde cualquier hilo. |
render_png(path, *, size=None, scale=2.0) |
Renderiza la vista a PNG sin abrir ventana. scale es la densidad de píxeles (2.0 ≈ pantalla Retina). |
pilot(*, size=None, scale=1.0, inspector=False) |
Ejecuta la app sin ventana y devuelve un Pilot para simular interacción en tests. Con inspector=True, el inspector se abre con pilot.press("f12") como en la ventana. |
watch(main=None) |
Activa el hot reload del módulo main (por defecto, __main__) sin vigilar los archivos y devuelve su Reloader. Para probarlo con pilot(): reloader.check() detecta lo guardado y el siguiente frame lo aplica. |
spawn(coro) |
Ejecuta una corrutina en un bucle asyncio en segundo plano (un hilo propio, creado la primera vez). Devuelve un concurrent.futures.Future con su resultado. |
El elemento raíz siempre ocupa la ventana completa. Si no defines bg, color o font_family
en la raíz, se toman del tema ($background, $text y la fuente del tema).
Window¶
app = App(view, title="Notas", size=(420, 380))
detalle = app.window(detalle_view, title="Detalle", size=(320, 240))
Una ventana adicional, creada con App.window(...). Tiene su propia vista y comparte con
la app el tema, el movimiento y los Signal: un cambio se ve en todas las ventanas a la vez. Las
creadas antes de run() se abren al arrancar; las creadas en marcha, al momento.
La ventana principal también se maneja así, a través de app.main (mismo Window, misma
API): app.main.title = "…" en marcha, app.main.close() para ocultarla si hay bandeja,
app.main.open() para volver a abrirla, o app.main.on_close = fn para confirmar antes de
cerrar. Su vista es la de la App (el hot reload la mantiene).
| Miembro | Descripción |
|---|---|
view |
La función de la vista de esta ventana (se puede sustituir). |
title |
El título. Asignarlo en marcha lo cambia al momento; seguro desde cualquier hilo. |
size |
(ancho, alto) en píxeles lógicos. Asignarlo en marcha redimensiona la ventana. |
resizable |
Si el usuario puede redimensionarla (se aplica al abrirla). |
on_close |
Se llama al intentar cerrarla (el botón del sistema o close()); si devuelve un valor falso, el cierre se cancela —por ejemplo, para pedir confirmación con un dialog. |
is_open |
Si la ventana está abierta (o pendiente de abrir al arrancar la app). |
open() / close() |
La abre o la cierra sin olvidar su vista; seguras desde cualquier hilo. |
render_png(path, *, size=None, scale=2.0) |
Su vista a PNG, como App.render_png. |
pilot(*, size=None, scale=1.0, inspector=False) |
Su vista con un Pilot para tests de interacción. |
La app termina al cerrarse su última ventana (o con app.quit()), y el inspector (F12) es
independiente en cada ventana. Un ejemplo completo: examples/windows.py.
Diálogos nativos¶
from aurora import dialogs
ruta = dialogs.open_file(title="Abrir informe", filters=[("Texto", ["txt", "md"])])
Los diálogos del sistema para elegir archivos o carpetas:
| Función | Descripción |
|---|---|
open_file(*, title=None, directory=None, filters=None) |
Pide un archivo existente; devuelve su ruta o None si se cancela. |
open_files(*, title=None, directory=None, filters=None) |
Igual, con selección múltiple; devuelve una lista (vacía si se cancela). |
save_file(*, title=None, directory=None, default_name=None, filters=None) |
Pide dónde guardar y devuelve la ruta, o None. No escribe nada: guardar el contenido es cosa de quien llama. |
pick_folder(*, title=None, directory=None) |
Pide una carpeta; devuelve su ruta o None. |
filters son pares (nombre, extensiones): [("Imágenes", ["png", "jpg"]), ("Todo", ["*"])].
Son síncronas: con el diálogo abierto la app espera (el sistema corre su propio bucle de
mensajes) y el GIL se suelta, así que los hilos y las corrutinas de app.spawn siguen trabajando.
La ventana desde la que se llaman (su vista o uno de sus manejadores) es su padre: el diálogo sale
modal sobre ella. Los pinta el sistema: en Linux usan el portal de escritorio (o zenity si no
hay portal) y en macOS aparecen con la app en primer plano. En los tests, sustitúyelas por un doble
—monkeypatch.setattr(dialogs, "open_file", ...)—: un test sin pantalla no puede abrir uno de
verdad.
Bandeja del sistema¶
from aurora import menus
app.tray(
icon="icono.png",
tooltip="Notas",
menu=[
menus.item("Abrir ventana", abrir),
menus.separator(),
menus.item("Salir", app.quit),
],
on_click=lambda button: abrir(),
)
| API | Descripción |
|---|---|
app.tray(icon, *, tooltip=None, menu=None, on_click=None, on_double_click=None, template=False) |
Pone (o reemplaza) la bandeja. icon es una ruta o los bytes de un PNG/SVG (los dibuja Skia, como image). menu es una descripción de menus. on_click(button) / on_double_click(button) reciben "left", "right" o "middle". template=True (macOS) pinta el icono con el color del sistema: dibújalo en blanco y negro con transparencia. |
app.tray_hide() |
Quita la bandeja. |
Mientras hay bandeja, la app sigue viva aunque se cierren todas las ventanas (es lo que
espera una app de bandeja): app.quit() la termina. run(keep_alive=True) también vale sin
bandeja, para una app que trabaja sin ventanas.
Con menú, el clic izquierdo suele abrirlo —en macOS siempre—; on_click se dispara con los clics
que no abre menú. En Linux la bandeja usa StatusNotifierItem: en GNOME hace falta la extensión
AppIndicator, y algunos escritorios no muestran la bandeja en absoluto.
Notificaciones¶
| API | Descripción |
|---|---|
app.notify(title, body="", *, icon=None, on_click=None) |
Muestra una notificación del sistema, con la identidad de la app. icon es una ruta a un PNG/SVG (donde el sistema lo admita). on_click, si se indica, se llama al pulsar el aviso. |
app.register_app_id(name=None) |
(Windows) Registra la identidad de la app con un acceso directo del menú Inicio: %APPDATA%\...\Start Menu\Programs\<name>.lnk. Devuelve False fuera de Windows o si no se pudo. |
Se puede llamar desde cualquier hilo —por ejemplo desde una tarea de app.spawn() con la ventana
cerrada— y también sin app en marcha. Si el sistema no admite notificaciones, se avisa por stderr
y la app sigue: en Linux hace falta un demonio de notificaciones (GNOME y KDE lo traen) y en macOS
una app firmada (un script suelto puede no poder mostrarlas).
El clic en el aviso. Con on_click, pulsar el cuerpo de la notificación llama a ese callback
(sin argumentos) desde un hilo propio: cambiar un Signal despierta la ventana sola. En Linux el
aviso declara la acción default —la que los servidores asocian al cuerpo y no dibujan como
botón—, y en macOS hace falta la app en marcha (run()) para que el sistema entregue el clic.
Cerrar el aviso sin pulsarlo no llama a nada.
Windows y la identidad del aviso. Windows firma cada notificación con el AppUserModelID (AUMID) de quien la envía; sin uno propio, la identidad de reserva es la de PowerShell y el aviso sale como «Windows PowerShell». Por eso:
App(..., app_id="Mi.Aplicacion")fija la identidad (avisos y agrupación de la barra de tareas). En una app empaquetada (sys.frozen, es deciraurora build), si no la indicas se deriva del título (aurora.mi-aplicacion) y se registra sola al arrancar.- El registro es un acceso directo del menú Inicio con esa identidad: es lo que Windows exige
a las apps sin instalador (y lo que le da nombre e icono al aviso).
app.register_app_id()lo hace a mano; en desarrollo apunta apython.exe+ el script, útil para probar la atribución. En un script sin identidad, el aviso lo firma «Windows PowerShell» (cosmético).
Menús nativos¶
app = App(
view,
menu=menus.bar(
menus.submenu(
"Archivo",
menus.item("Abrir…", abrir, shortcut="mod+o"),
menus.item("Guardar", guardar, shortcut="mod+s"),
menus.separator(),
menus.item("Salir", app.quit),
),
menus.submenu("Edición", menus.predefined("copy"), menus.predefined("paste")),
),
)
| Constructor | Descripción |
|---|---|
menus.item(label, on_click, *, shortcut=None, enabled=True) |
Una opción; llama a on_click() al elegirla. shortcut es un atajo del sistema: "ctrl+o", "mod+s" (mod es Control fuera de macOS y Comando en macOS), "f5", "shift+alt+f"… |
menus.separator() |
Una línea separadora. |
menus.submenu(label, *items) |
Un submenú con sus entradas. |
menus.predefined(name) |
Una entrada estándar del sistema: "copy", "paste", "cut", "select_all", "undo", "redo", "delete", "minimize", "maximize", "fullscreen", "close_window", "quit", "about", "hide", "hide_others", "show_all", "services" o "bring_all_to_front". |
menus.bar(*items) |
La barra de la app, para App(menu=...); es azúcar de una lista. |
La misma descripción vale para los tres menús nativos:
- La barra de la app:
App(view, menu=menus.bar(...)), en Windows y macOS. En macOS el núcleo añade delante el submenú estándar de la app (Acerca de, Ocultar, Salir…). Los atajos de la barra los gestiona el sistema, así que la app no los ve como teclas: no los repitas enon_key. - El menú de la bandeja:
app.tray(icon, menu=[...]). - Los menús contextuales: la propiedad
on_context_menude cualquier elemento —clic derecho— con una lista o un callable que la devuelve (se evalúa al abrir el menú, así puede depender del estado):
card(
on_context_menu=[
menus.item("Copiar", copiar),
menus.separator(),
menus.item("Eliminar", eliminar, enabled=hay_seleccion()),
],
)[...]
En Linux no hay menús nativos (requieren GTK): la barra y los contextuales no aparecen y el patrón
es el componente menu dentro de la interfaz. En macOS, con menú, el clic izquierdo de la bandeja
abre su menú.
Signal¶
| API | Descripción |
|---|---|
count() / count.value |
Lee el valor actual. |
count.set(v) |
Asigna un valor. Si es igual al actual (==), no hace nada. |
count.update(fn) |
Atajo para count.set(fn(count())). |
count.subscribe(fn) |
Llama a fn(valor) en cada cambio. Devuelve una función para cancelar la suscripción. |
La interfaz se reconstruye tras cada evento de usuario y tras cada set que cambie un valor;
los @component que no dependan de lo que cambió se reutilizan.
set es seguro desde cualquier hilo o corrutina: despierta el bucle de eventos, así que un
Signal actualizado por un hilo de trabajo o por una tarea de app.spawn() se ve en pantalla sin
esperar a que el usuario mueva el ratón.
status = Signal("cargando…")
async def load():
data = await fetch_data()
status.set(f"{len(data)} filas")
app = App(view)
app.spawn(load())
app.run()
component¶
@component
def fila(tarea: Tarea, seleccionada: bool):
return row(key=f"tarea-{tarea.id}", bg="$surface" if seleccionada else None)[...]
view() se vuelve a llamar entera tras cada cambio, pero un @component no se reconstruye si
sus argumentos son iguales (==) a los de la última vez y no cambió ningún Signal que leyera, al
construirse o al resolver sus textos con lambda. Devuelve el mismo Element, que recuerda su
serialización, y el núcleo reutiliza el nodo ya procesado: el coste de esa rama en Python y en el
parseo desaparece. Es la forma de escalar listas grandes: en una de 10 000 filas, cambiar la
selección reconstruye 2 filas en vez de 10 000.
- Pasa como argumento lo que distingue a cada instancia (
seleccionada) en vez de leer unSignalcompartido dentro: un componente que leeselected()depende de él y se reconstruye cada vez que cambia. - Los argumentos deben ser hashables (números, cadenas, tuplas, dataclasses congeladas). Con argumentos no hashables el componente funciona, pero no se memoriza.
- Depende solo de sus argumentos y de
Signal. Si lee estado mutable normal (una lista global, un atributo de un objeto), no se entera de sus cambios. - No pases lambdas creadas en cada llamada como argumento: son distintas cada vez. Créalas dentro
del componente (
on_click=lambda: selected.set(tarea.id)). - Un componente puede contener otros: si cambia el de dentro, se reconstruye también el de fuera.
- Se olvidan las instancias que no aparecen en la última reconstrucción de la vista.
Pilot (tests de interacción)¶
app.pilot() ejecuta la app sin ventana con el mismo runtime que la ventana real
(hit-testing, burbujeo, capas, reconstrucción) y simula el puntero frame a frame. Las coordenadas
están en píxeles lógicos y cada acción avanza un frame.
def test_counter():
pilot = App(view, size=(400, 300)).pilot()
pilot.click("add")
assert count() == 1
| API | Descripción |
|---|---|
click(key) |
Clic en el centro del nodo key, como una persona: si otro nodo lo tapa, el clic llega a ese otro. LookupError si el nodo no se pintó. |
click_at(x, y) |
Mueve el puntero, pulsa y suelta. |
drag(key, dx=0, dy=0, steps=4) |
Pulsa en el centro de key, mueve el puntero (dx, dy) en steps pasos y suelta. |
hover_files(files) |
Archivos del sistema que entran arrastrándose en la ventana (None: salen sin soltar). Llama a los on_drop_hover. |
drop_files(files, key=None, at=None) |
Suelta archivos sobre el centro de key, en at=(x, y) o, sin ninguno, sin posición (como en macOS y Linux). Devuelve True si un on_drop los recibió. Ver Soltar archivos. |
cursor |
Cursor del ratón en el último frame: "default", "pointer", "text", "grab", "grabbing", "col-resize", "row-resize" o "crosshair" (modo selección del inspector). |
move_to(x, y) / leave() |
Mueve el puntero / lo saca de la ventana. |
mouse_down() / mouse_up() |
Pulsa / suelta el botón izquierdo por separado. |
wheel(dy=0, dx=0, *, lines=False) |
Rueda bajo el puntero; positivo = hacia el final del contenido. En píxeles (trackpad, inmediato) o, con lines=True, en pasos de rueda (animado). |
scroll(key, dy=0, dx=0) |
Mueve el puntero al centro de key y desplaza esos píxeles. |
scroll_offset(key) |
(x, y) desplazados del scroll con esa key, o None. |
chart_view(key) |
Ventana visible del eje x del chart con esa key, en unidades de datos (sin zoom, su dominio completo). |
press(combo) |
Pulsa y suelta una tecla o combinación: "tab", "shift+tab", "enter", "ctrl+s", "a". |
key_down(combo) / key_up(combo) |
Pulsa / suelta por separado (p. ej. mantener Espacio). |
type(texto) |
Escribe en el campo enfocado, grafema a grafema, como desde el teclado ("\n" = Enter). |
ime_preedit(texto) / ime_commit(texto) |
Simulan la composición y la confirmación de un IME. |
selection(key) |
Selección del campo key en caracteres: (inicio, fin), iguales si solo hay cursor. |
a11y_tree() |
El árbol de accesibilidad del último frame —lo que ve un lector de pantalla—: una lista de dicts por nodo con id, key, role, label, value, estados (checked, selected, expanded…), bounds en píxeles lógicos, actions, children y focused. Lo decorativo no aparece. |
a11y_action(key, action, value=None) |
Pide una acción como un lector de pantalla sobre el nodo key: "click", "focus", "blur", "set_value" (con value: texto o número), "increment", "decrement", "expand", "collapse" o "scroll_into_view". |
clipboard |
Portapapeles del piloto, en memoria (se puede leer y asignar); no toca el del sistema. |
focus(key) |
Enfoca key como si se llegara con el teclado. LookupError si no se pintó o no es enfocable. |
focused |
Key del nodo enfocado, o None. |
tooltip |
Texto del tooltip visible, o None. Avanza el tiempo con frame(dt=0.6) para que aparezca. |
hovered |
Key del nodo interactivo bajo el puntero, o None. |
rect(key) |
(x, y, ancho, alto) del nodo en el último frame, o None. |
frame(dt=1/60) |
Avanza un frame. Devuelve True si quedan transiciones en curso. |
settle() |
Avanza frames hasta que terminen todas las transiciones. |
resize(ancho, alto) |
Cambia el tamaño de la "ventana". |
png(path) |
Guarda el último frame como PNG. |
inspection |
Estado del inspector si el piloto se creó con inspector=True (si no, None): open, pick (modo selección), selected y hovered (keys), rect (caja del nodo elegido en el último frame) y frames (lista de stats de los últimos frames, solo con el panel abierto). |
stats |
Coste del último frame por fases, en milisegundos: build (tu view() y la serialización), parse, style (comparar el árbol nuevo con el anterior), layout, draw (las funciones draw de los canvas y la grabación de los chart), paint (composición y pintura) y total (la suma de todas); además nodes (nodos del árbol), rebuilt (si se reconstruyó la vista; si no, build, parse y style valen 0), layout_cached (si se reutilizó el layout del frame anterior) y damage, la zona repintada (x, y, ancho, alto) o None si el frame no pintó nada. Lo usa la suite de benchmarks. |
Las excepciones de la vista y de los manejadores se propagan al test (con ventana, se imprimen y la app sigue viva).
Inspector y profiler de frames¶
Con el inspector activado (App(view, inspector=True) o AURORA_INSPECTOR=1), F12 acopla un
panel a la derecha de la ventana; la app ocupa el resto, sin quedar tapada. Desactivado (lo
habitual en una app distribuida), no cuesta nada y F12 no hace nada.
| Atajo | Acción |
|---|---|
| F12 | Abre y cierra el panel. |
| Ctrl+Mayús+C (Cmd+Mayús+C en macOS) | Entra y sale del modo selección (lo abre si está cerrado). También con el botón ⌖ del panel. |
| Escape | Sale del modo selección. |
- Árbol: los nodos de la vista en orden, con su tipo (
row,column,scroll,text,input,image,canvas,chart), su key si es explícita y su texto. Las ramas se pliegan con la flecha. Un clic elige el nodo y muestra debajo su caja (x,y, ancho y alto en el último frame), su texto, su estilo tal como llegó al núcleo (tokens ya resueltos, con muestra de color) y sus eventos (on_click,on_key…). La lista es virtual: árboles de miles de nodos van fluidos. - Modo selección: pasar el ratón por la app resalta el nodo pintado más arriba (contenido en azul, padding en verde, etiqueta con tipo, key y tamaño) sin hover ni llamadas a Python; un clic lo elige sin llegar a la app y el árbol se despliega y desplaza hasta él. El nodo elegido queda con un contorno rosa.
- Frames: coste por fases de los últimos 240 frames (
build—tuview()—,parse, estilo, layout,drawy pintura) en barras apiladas frente al presupuesto de 60 fps (16,7 ms), con el último, la media, el p95 y el máximo, el desglose por fase y los datos del último frame (nodos, si reconstruyó la vista, si reutilizó el layout y la zona repintada). Son tiempos de CPU de Aurora, sin la presentación en pantalla. Los frames que no reconstruyen ni pintan nada no cuentan. Mientras llegan frames nuevos, el panel se refresca cada 250 ms (esos refrescos no se registran).
El panel se construye con las mismas primitivas que la app, así que su coste aparece en los
frames mientras está abierto (con la vista cerrada no hay ninguno). Con el panel abierto, la raíz
de la app pasa a ser hija de una fila: las keys por ruta llevan delante 0/ ("0/0/1" en vez de
"0/1"); las explícitas no cambian.
Hot reload¶
Con el hot reload activado, al guardar el script de la app o cualquier módulo suyo, la ventana
se actualiza con el código nuevo en menos de medio segundo, sin cerrarse y sin perder el estado
de los Signal:
aurora dev app.py # o: AURORA_RELOAD=1 uv run app.py
aurora dev --inspector app.py # también con el inspector (F12)
aurora dev script [args...] ejecuta el script como python script (con __name__ == "__main__"
y los argumentos que sigan) y activa el hot reload; las opciones de aurora dev van antes del
script. También se activa con App(view, reload=True).
Qué se recarga. Se vigila el script principal y los módulos que importa desde su carpeta (y
subcarpetas), salvo los instalados (site-packages, el entorno virtual) y Aurora. Al cambiar uno,
se vuelven a ejecutar ese módulo, los módulos de la app que usan algo suyo (una función, una clase,
un objeto) y el script principal. Ahí App(...) y app.run() no abren otra ventana: la app en
marcha pasa a usar la view y el theme nuevos. El título y el tamaño no cambian. Con varias
ventanas, se emparejan con las del código nuevo por orden de creación (las demás se cierran o se
abren), y cada una pasa a usar la vista nueva.
Qué se conserva. Cada Signal del código nuevo que esté en el mismo sitio que uno del anterior
—una variable global, o un atributo o elemento (hasta 4 niveles) de un objeto de una clase de la
app, un dict o una lista— se sustituye por el antiguo: conserva su valor y su identidad, así
que los hilos, las corrutinas de app.spawn y los módulos que lo tenían guardado siguen usando el
mismo. Se queda con las suscripciones (subscribe) del código nuevo. Si el valor ya no es del
tipo del valor inicial nuevo (pasaste de Signal(0) a Signal("")), gana el nuevo. Cambiar solo
el valor inicial no reinicia el estado. También se conservan el foco, el scroll y las animaciones
de los nodos cuya key no cambie, como en cualquier reconstrucción.
Qué no se conserva. Los Signal locales (creados dentro de una función), el estado mutable
que no esté en un Signal y las instancias de clases redefinidas que guarde un Signal (siguen
siendo de la clase anterior: isinstance con la nueva da False).
Errores. Si el código nuevo no compila o falla al ejecutarse, los módulos vuelven a como
estaban y la ventana muestra el traceback (también se imprime en la consola). Con el hot reload
activado, una excepción en view() se muestra igual en vez de cerrar la app. Al guardar de nuevo
se reintenta. Los manejadores de eventos que fallan se imprimen, como siempre.
Desactivado no cuesta nada; activado, un hilo mira las fechas de modificación cada 300 ms. Es una
herramienta de desarrollo: una app empaquetada con aurora build no debería activarlo.
Primitivas¶
Todas devuelven un Element. Los hijos se añaden con corchetes:

Dentro de los corchetes se aceptan elementos, listas (se aplanan), strings (se convierten en
text) y None/False (se ignoran, para poder escribir condición and elemento).
| Primitiva | Descripción |
|---|---|
box(**estilo) |
Contenedor genérico. Apila sus hijos en columna por defecto. |
column(**estilo) |
Contenedor con direction="column". |
row(**estilo) |
Contenedor con direction="row". |
text(contenido, **estilo) |
Texto. contenido puede ser un str o un callable (lambda: f"{x()}"), que se evalúa en cada reconstrucción. |
spacer() |
Caja con grow=1: ocupa todo el espacio libre del eje principal. |
scroll(*, axis="y", **estilo) |
Contenedor desplazable (rueda, trackpad y barra arrastrable). Ver abajo. |
image(src, *, fit="contain", **estilo) |
Imagen de un archivo o de bytes. Ver abajo. |
canvas(draw, **estilo) |
Lienzo de dibujo inmediato. Ver abajo. |
chart(y, *, kind="line", **estilo) |
Gráfico nativo (línea, área, barras, dispersión) alimentado por arrays. Ver abajo. |
image(src, *, fit="contain", **estilo)¶
Muestra una imagen PNG, JPEG, WebP, GIF (primer fotograma), BMP, ICO o SVG. src es una
ruta (str o Path) o bytes con el archivo ya en memoria (p. ej. una gráfica generada con
matplotlib).
image("assets/logo.svg", width=32)
image(cover_path, height=160, fit="cover", radius="$lg")
image(png_bytes)
- Tamaño: sin
widthniheight, ocupa su tamaño natural (1 píxel de la imagen = 1 píxel lógico) sin pasar del ancho disponible, comomax-width: 100%en CSS. Con uno de los dos, el otro sale de su proporción. Sinwidth, no se estira en el eje cruzado de su contenedor. fitdecide cómo encaja cuando fijas los dos lados:"contain"(entera, sin deformar, con márgenes si hace falta),"cover"(llena la caja recortando lo que sobra),"fill"(estirada) o"none"(tamaño natural, centrada y recortada).- Se recorta a las esquinas del elemento: con
radius="$full"yfit="cover"tienes un avatar. - Los SVG se pintan como vectores: se ven nítidos a cualquier tamaño y densidad de pantalla. Su
tamaño natural sale de
width/heighto, si no los tienen, delviewBox. - Cada imagen se decodifica una vez y se reutiliza entre reconstrucciones. Si el archivo cambia en disco, se vuelve a leer en la siguiente reconstrucción.
- Un archivo que no existe lanza
FileNotFoundError, y uno que no se puede decodificar,ValueError, ambos con la ruta en el mensaje. - Una ruta relativa se resuelve desde el directorio de trabajo. Para los recursos de tu app usa
resource(), que funciona igual con Python y en el ejecutable empaquetado.
canvas(draw, **estilo)¶
Lienzo de dibujo inmediato: draw(c) recibe un Canvas del tamaño de la caja y dibuja en él
con comandos sueltos, como un <canvas> de HTML. Para gráficos, indicadores, diagramas o
cualquier cosa que no sea una composición de cajas.
from aurora import Canvas, canvas
def sparkline(c: Canvas):
step = c.width / (len(values) - 1)
points = [(i * step, c.height - v * c.height) for i, v in enumerate(values)]
c.polyline(points, stroke="$primary", stroke_width=2)
c.text(c.width, 0, "máx.", align="right", size=11, color="$muted")
canvas(sparkline, height=60, bg="$surface", radius="$md", padding=8)

- Coordenadas: píxeles lógicos con el origen en la esquina superior izquierda de la caja de
contenido (dentro del
padding) y el eje y hacia abajo.c.widthyc.heightson su tamaño. Lo que se sale se recorta (también a las esquinas redondeadas). - Tamaño: sin
height,grow,min_heightnimax_heightmide 200 px de alto; a lo ancho se estira como cualquier caja. Admite el estilo de una caja (bg,radius,border_*,shadow…), manejadores (on_click…) e hijos con[...], que se pintan encima del dibujo. - Cuándo se llama a
draw: después del layout (por eso ya conoce su tamaño), cada vez que la vista se reconstruye y cuando el lienzo cambia de tamaño. Hover, scroll y animaciones de otros nodos no lo llaman: el dibujo grabado se repinta sin volver a Python. Dentro de un@componentque no se reconstruye,drawsolo se repite si cambia el tamaño o algúnSignalque lea la propia función. - En lote: los comandos se acumulan en Python y cruzan al núcleo de una vez, donde se graban
en un
Picturede Skia. Las listas de puntos viajan como un bloque defloat32; un array de numpy de forma(n, 2)se convierte sin recorrerlo en Python. - Un comando inválido (color desconocido,
dashimpar…) lanzaValueError; una excepción endrawse propaga como las de la vista.
Métodos de Canvas. Los colores aceptan tokens ("$primary") y hex con alfa. Las figuras se
rellenan con fill, se perfilan con stroke (de grosor stroke_width) o ambas cosas; sin
ninguno de los dos usan $primary (rellenas las figuras cerradas, trazadas las líneas y arcos).
dash=(on, off, ...) hace el trazo discontinuo.
| Método | Descripción |
|---|---|
clear(color="$background") |
Rellena todo el lienzo. |
rect(x, y, w, h, *, radius=0, fill, stroke, stroke_width=1, dash) |
Rectángulo, con esquinas redondeadas opcionales. |
oval(x, y, w, h, ...) / circle(cx, cy, r, ...) |
Elipse inscrita en el rectángulo / círculo. |
arc(cx, cy, r, start, sweep, *, pie=False, ..., cap="butt") |
Arco (grados, 0 = derecha, sentido horario). pie=True lo cierra por el centro. |
line(x1, y1, x2, y2, *, stroke="$primary", stroke_width=1, cap="butt", dash) |
Segmento. |
polyline(points, *, stroke, fill, stroke_width=1, closed=False, cap, join="round", dash) |
Línea quebrada por [(x, y), ...] o un array (n, 2). |
polygon(points, *, fill, stroke, ...) |
Polígono cerrado y relleno. |
path(path, *, fill, stroke, ...) |
Trazado libre (Path, abajo). |
text(x, y, content, *, color="$text", size=14, weight=400, family=None, align="left", baseline="top") |
Una línea de texto. align ("left", "center", "right") la coloca respecto a x; baseline ("top", "middle", "alphabetic", "bottom") respecto a y. La familia por defecto es la del tema. |
save() / restore() / saved() |
Guardan y recuperan la transformación y el recorte. with c.saved(): ... hace las dos cosas. |
translate(dx, dy) / scale(sx, sy=None) / rotate(grados) |
Transforman lo que se dibuje después. |
clip_rect(x, y, w, h) |
Recorta lo que se dibuje después (hasta el próximo restore). |
cap: "butt", "round" o "square"; join: "miter", "round" o "bevel".
Path() construye trazados encadenando move_to(x, y), line_to(x, y),
quad_to(cx, cy, x, y), cubic_to(c1x, c1y, c2x, c2y, x, y), arc(cx, cy, r, start, sweep) y
close():
chart(y, *, x=None, kind="line", **estilo)¶
Gráfico nativo: línea, área, barras o dispersión, con los ejes, la rejilla y la leyenda calculados solos. Los datos se leen de los arrays sin copiarlos.
from aurora import chart
chart(ventas(), height=160) # una línea
chart(df, names=["p50", "p95"], kind="area") # varias series de un DataFrame
chart(cierres, labels=DIAS, kind="bar", height=140)
chart(ys, x=xs, kind="scatter", point_size=3)
chart(muestras, zoom=True) # millones de puntos: rueda para acercar, arrastra para mover
- Datos:
ypuede ser un array de numpy, unarray.array, unmemoryviewo una lista; unaSerieso unDataFramede polars (o cualquier cosa conto_numpy(), como pandas — un DataFrame aporta una serie por columna); una lista de series; o un array 2-D(n, m), una serie por columna. Los datos cruzan al núcleo sin copiarse: el gráfico lee la memoria del array, así que mutarlo en sitio y reconstruir la vista basta para actualizarlo (los de polars se convierten conto_numpy(), que comparte memoria cuando el tipo lo permite). Los valores que falten (NaN) se saltan y, en una línea o un área, la cortan. - Millones de puntos: al grabar, cada serie se reduce en Rust con LTTB (Largest-Triangle-Three-Buckets) a ~2 puntos por píxel: el perfil y los picos se conservan y solo se recorren los puntos visibles (con x ordenada, por búsqueda binaria). Un gráfico de un millón de puntos se graba en milisegundos y se mueve a 60 fps.
- Zoom y pan: con
zoom=True, la rueda acerca y aleja sobre el puntero (el dato bajo el cursor no se mueve), el arrastre desplaza la ventana, la rueda horizontal (trackpad) también desplaza y el doble clic vuelve a la vista completa. El eje y se ajusta a lo visible salvo que fijesy_min/y_max; al llegar a un tope, la rueda pasa alscrollde fuera. La ventana es estado del gráfico (sukey) y sobrevive a las reconstrucciones;Pilot.chart_view(key)la lee en los tests. - Series:
coloradmite un color o una lista (uno por serie; sin él, rota la paleta del tema) ynamesdibuja la leyenda de arriba. - Ejes: el de x son posiciones 0, 1, 2… salvo que des
x(numérico) olabels(categorías: meses, días…). El de y sale de los datos, con ticks redondos, o dey_min/y_max(los dos juntos deben ser coherentes). Las barras siempre nacen del cero y, con varias series, se agrupan (una al lado de otra).grid=Falsequita la rejilla. - Aspecto:
stroke_width(grosor de la línea, 2),point_size(radio de la dispersión, 3.5),bar_width(fracción del hueco de cada barra, 0.7),label_size,label_colorygrid_color; admite el estilo de una caja (bg,radius,padding,border_*…) y manejadores comoon_click(que recibe el clic en el gráfico, no en cada barra). - Tamaño: sin
height,grow,min_heightnimax_heightmide 200 px de alto; a lo ancho se estira como cualquier caja (en unarow, dalegrow=1owidth). - Cuándo se vuelve a grabar: cuando la vista se reconstruye con otros datos u opciones, cuando
cambia su caja o cuando la rueda o el arrastre mueven la ventana. Hover, scroll y animaciones de
otros nodos lo repintan sin releer los datos. Dentro de un
@component, solo si cambian sus argumentos o algúnSignalque lea. - Un dato no numérico (texto, complejos…), unas
labelsque no encajen con los puntos o unakinddesconocida lanzanValueError(TypeErrorsi los datos no son ni números ni arrays).
import numpy as np
from aurora import chart
rng = np.random.default_rng(7)
precios = 100 + np.cumsum(rng.normal(0.15, 1, size=(250, 2)), axis=0) # dos series, una por columna
chart(precios, names=["Aurora", "Índice"], height=200)

Ejemplo completo en examples/charts.py.
scroll(*, axis="y", **estilo)¶
Recorta su contenido a su caja (respetando radius y border_width) y lo desplaza en el eje
axis: "y" (vertical), "x" (horizontal) o "both".
- Apila sus hijos en el eje que se desplaza (
direction="row"conaxis="x") y no los encoge para que quepan: lo que sobra se desplaza. Elpaddingforma parte del contenido desplazable. - Uno vertical sin
heightnimax_heightocupa el espacio libre (grow=1). Conheightomax_heighttiene ese tamaño. - La rueda del ratón se desplaza con suavizado; el trackpad, píxel a píxel (en macOS, con la inercia
del sistema). Al llegar al límite, el desplazamiento pasa al
scrollque lo contiene. Una rueda vertical también desplaza unscrollsolo horizontal. - La barra se superpone al contenido sin ocupar espacio. Aparece al desplazarse o al acercar el puntero al borde, y se oculta sola. Se puede arrastrar; pulsar fuera del pulgar lo lleva al puntero.
- Los hijos que quedan fuera de la zona visible no reciben clics ni hover.
- Dale una
keypara conservar la posición si elscrollcambia de sitio en el árbol. Si el contenido se encoge, la posición se ajusta sola. on_scrollrecibe unScrollEvent(x,y: desplazamiento;width,height: tamaño de la zona visible, en píxeles lógicos) en el primer frame y cada vez que alguno cambia. A diferencia de los demás manejadores no reconstruye la vista por sí solo (se llama en cada frame de desplazamiento): si cambias unSignalen él, la vista se reconstruye en ese mismo frame. Para listas muy largas,virtual_listya lo usa por ti.
scroll(key="log", max_height=240, gap=6)[[text(line) for line in lines()]]
scroll(axis="x", gap=12)[[card(width=200)[...] for item in items()]]
Además de las propiedades de estilo, todos los elementos aceptan:
| Argumento | Descripción |
|---|---|
on_scroll |
En un scroll: callable que recibe un ScrollEvent cuando cambian el desplazamiento o la zona visible. Ver scroll. |
on_click |
Callable sin argumentos. Hace al elemento interactivo: cursor de mano, estados hover/pressed y clics. |
on_drag |
Callable que recibe un DragEvent. Pulsar sobre el elemento empieza un arrastre: le llegan todos los movimientos hasta soltar, aunque el puntero salga de su caja. Si el puntero llega a moverse, el gesto no cuenta como clic. Hace al elemento interactivo (hover/pressed) pero no enfocable. |
on_key |
Callable que recibe un KeyEvent. Recibe las teclas cuando el foco está en el elemento o en uno de sus descendientes (la raíz las recibe siempre). Devuelve True para darla por atendida. |
on_focus / on_blur |
Callables sin argumentos: el elemento gana / pierde el foco. |
on_drop |
Callable que recibe la lista de rutas (list[str]) de los archivos que se sueltan desde el explorador del sistema sobre el elemento o sus hijos. Ver Soltar archivos. |
on_drop_hover |
Callable que recibe la lista de rutas cuando un arrastre de archivos entra en la ventana y None cuando sale o se suelta. Para resaltar las zonas donde soltar. |
on_<evento> |
Todo argumento que empiece por on_ se trata como manejador, no como estilo. |
focusable |
bool. Si el elemento recibe el foco con Tab y con clic. Por defecto, los que tienen on_click. |
autofocus |
bool. Toma el foco al aparecer en la vista (p. ej. el primer botón de un diálogo). Solo al aparecer: no lo roba en cada reconstrucción. |
role |
Rol de accesibilidad para los lectores de pantalla: "button", "checkbox", "switch", "radio", "radiogroup", "slider", "textbox", "combobox", "option", "tab", "tablist", "dialog", "menu", "menuitem", "list", "listitem", "table", "row", "cell", "columnheader", "progressbar", "image", "heading", "status", "link", "region", "separator"… (los nombres de ARIA en minúscula). Un rol desconocido lanza ValueError. Ver Accesibilidad. |
label |
Nombre con el que el lector anuncia el elemento. Por defecto, su texto (text, o el de un botón o casilla). |
description |
Texto secundario de ayuda; si no se indica, el tooltip hace de descripción. |
value |
Valor anunciado: texto ("3 de 10") o número (0.5). En un campo de texto, el valor es lo que hay escrito. |
checked |
bool. Estado de checkbox y switch (las casillas y los interruptores lo ponen solos). |
selected |
bool. Estado de tab y option (pestañas, desplegables). |
expanded |
bool. Estado de combobox y button de acordeón. |
level |
Nivel de un heading (1 = título principal). |
live |
"polite" o "assertive": región viva — sus cambios se anuncian solos (lo usan toast). |
position_in_set / set_size |
Posición y tamaño del conjunto: «elemento 3 de 100» (radio, virtual_list y table los ponen solos). |
sort |
"ascending" o "descending" en una cabecera ordenada (table). |
modal |
bool. El diálogo bloquea el resto de la interfaz (lo pone dialog). |
value_min / value_max / value_step |
Rango de un slider o una barra de progreso, y paso de las acciones de ajuste. |
hidden |
bool. Fuera del árbol de accesibilidad: decorativo (el contenido visual no cambia). |
alt |
En una imagen, texto alternativo (equivale a label). Sin alt ni label, la imagen es decorativa y no se anuncia. |
key |
Identidad estable. Conserva el estado de animación aunque el elemento cambie de posición en el árbol. Recomendado en listas dinámicas. Un elemento sin key recibe la de su padre seguida de su posición ("fila-7/0", "fila-7/1"…), así que los hijos siguen a su padre si este se mueve. |
DragEvent¶
Lo que reciben los manejadores on_drag, en píxeles lógicos:
| Atributo | Descripción |
|---|---|
phase |
"start" al pulsar, "move" en cada movimiento y "end" al soltar. |
x, y |
Posición actual del puntero en la ventana. |
dx, dy |
Desplazamiento desde donde se pulsó (no desde el último evento). |
ancho = Signal(240.0)
inicio = {}
def redimensionar(e: DragEvent):
if e.phase == "start":
inicio["ancho"] = ancho()
ancho.set(max(120, inicio["ancho"] + e.dx))
row()[
column(width=ancho())[...],
box(width=6, cursor="col-resize", bg_hover="$border", on_drag=redimensionar),
]
Soltar archivos¶
Arrastrar archivos desde el explorador del sistema hasta la ventana los entrega al elemento con
on_drop que hay bajo el puntero (o a su primer ancestro que lo tenga), como una lista de rutas
absolutas. Mientras el arrastre pasa sobre la ventana, todos los on_drop_hover de la vista
reciben esas rutas, y None al salir o al soltar:
arrastrando = Signal(False)
archivos = Signal[list[str]]([])
card(
padding=24,
border_width=2,
border_color="$primary" if arrastrando() else "$border",
on_drop=archivos.set,
on_drop_hover=lambda rutas: arrastrando.set(rutas is not None),
)[text("Suelta aquí tus CSV")]
- Dónde cae: en Windows, en el punto exacto donde se suelta. En macOS y Linux (X11) el sistema
no informa del punto durante el arrastre: los archivos van al primer elemento con
on_dropde la vista (en orden del árbol), así que con una sola zona se comporta igual en todos los sistemas. Por la misma razón,on_drop_hoveravisa a la vista entera, no a la zona de debajo. - Linux con Wayland: winit todavía no entrega los archivos arrastrados; ni
on_dropnion_drop_hoverse llaman (con XWayland sí). - Solo se entregan archivos y carpetas (no texto ni imágenes arrastradas desde otra app). Lo que haya en las rutas lo decide tu manejador: comprueba la extensión antes de abrirlas.
- En los tests,
pilot.hover_files(rutas)/pilot.hover_files(None)ypilot.drop_files(rutas, key=...)simulan el arrastre (sinkeyniat, sin posición, como en macOS y Linux);drop_filesdevuelve si algúnon_droplos recibió.
Teclado y foco¶
Un elemento enfocado recibe el teclado. Se enfoca con Tab / Shift+Tab (en orden de pintura,
volviendo al principio al final) o con un clic; un clic en una zona sin elementos enfocables
quita el foco. Al llegar con el teclado se dibuja un anillo de foco animado, del color
heredable focus_color ($primary por defecto); con un clic no, como :focus-visible en CSS.
Si el elemento está dentro de un scroll, este se desplaza para mostrarlo.
Cada tecla pasa primero por los on_key, desde el elemento enfocado hacia la raíz (sin foco, solo
la raíz). El primero que devuelva un valor verdadero la da por atendida. Si nadie la atiende, se
aplica el comportamiento por defecto:
| Tecla | Por defecto |
|---|---|
| Tab / Shift+Tab | Foco al siguiente / anterior. Dentro de una barrera capture=True (un modal), el foco no sale de ella. |
| Enter | Activa (on_click) el elemento enfocado. |
| Espacio | Activa el elemento enfocado al soltarla, con la animación de pulsado. Sin elemento activable, avanza una página. |
| ↑ ↓ ← → | Desplazan el scroll que contiene al foco o, sin foco, el que está bajo el puntero. |
| RePág / AvPág, Inicio / Fin | Desplazan una página / hasta el principio o el final. |
Para atajos globales, pon on_key en la raíz de la vista:
def on_key(e: KeyEvent):
if e.matches("mod+n"): # Ctrl+N (Cmd+N en macOS)
add_task()
return True
if e.matches("escape"):
modal.set(None)
return True
return None # el resto, comportamiento normal
def view():
return column(on_key=on_key)[...]
KeyEvent¶
| Atributo | Descripción |
|---|---|
key |
Nombre de la tecla: "Enter", "Tab", "Escape", "Space", "Backspace", "Delete", "ArrowUp", "Home", "PageDown", "F1"… o el carácter ("a", "A", "?"). |
text |
Texto que produce la tecla ("a", " "), o None (teclas de control, atajos con Ctrl/Alt/Cmd). |
ctrl, shift, alt, meta |
Modificadores pulsados. meta es Cmd en macOS y la tecla Windows en Windows. |
repeat |
True si es una repetición por mantener la tecla. |
matches(combo) |
True si la pulsación es exactamente esa combinación: "ctrl+s", "shift+tab", "escape", "mod+k" (mod = Cmd en macOS, Ctrl en el resto). Las letras no distinguen mayúsculas, y en los símbolos que requieren Shift ("?") no hace falta escribirlo. Una combinación mal escrita lanza ValueError. |
Alias aceptados en las combinaciones: esc, return, del, up/down/left/right, pgup/pgdn,
plus; y como modificadores control, option, cmd, super y win.
Accesibilidad¶
Aurora publica lo que describe la vista en el API de accesibilidad del sistema (UIA en Windows, NSAccessibility en macOS, AT-SPI en Linux) con AccessKit: los lectores de pantalla (Narrador, NVDA, VoiceOver, Orca) ven el árbol y sus acciones —activar, enfocar, escribir un valor, ajustar un deslizador, desplazarse hasta un elemento— llegan a los mismos manejadores que un clic o una tecla. Sin lector conectado el árbol ni se construye ni se actualiza.
- Los componentes declaran su semántica solos:
button(nombre y activación),checkboxyswitch(marcado),radio(grupo, posición y activo),slider(valor y rango),text_input/textarea(el valor real, incluido lo que se está escribiendo),select(valor, desplegado y opciones),tabs,menu,dialog(modal y título),progress,toast(región viva),virtual_list(posiciones) ytable(cabeceras, filas y ordenación).imageyavatarusanalt; sin él son decorativos. - Las primitivas también cuentan:
textes una etiqueta con su valor y cualquier elemento acepta las props de la tabla de Primitivas. Una caja sin rol, sin nombre y sin contenido no se anuncia: el andamiaje de layout no ensucia el árbol. - El texto interno de un control (la etiqueta de una casilla, el texto de un botón) no se anuncia dos veces: el nombre lo lleva el control.
- En los campos de texto, la acción «fijar valor» del lector escribe como si se tecleara (dispara
on_changey respeta un valor controlado). - Los tests pueden comprobar el árbol y simular acciones con
pilot.a11y_tree()ypilot.a11y_action(key, "click")(ver Pilot).
checkbox(acepto, "Acepto los términos", key="terms") # el lector dice «Acepto los términos, casilla»
table(tasks, cols, key="t") # la cabecera ordenada anuncia su orden y se activa con Enter
toast(message) # un aviso: se anuncia solo al aparecer (región viva)
Componentes¶
Construidos solo con primitivas: puedes leerlos en python/aurora/components/ (un módulo por
familia) y usarlos como plantilla para los tuyos. Todos aceptan propiedades de estilo extra, que
sobrescriben las suyas.
button(label, on_click=None, *, variant="primary", key=None, **estilo)¶
variant |
Aspecto |
|---|---|
"primary" |
Color de marca con brillo; acción principal. |
"soft" |
Superficie con borde; acciones secundarias. |
"ghost" |
Transparente hasta el hover; acciones terciarias. |
Por defecto: padding=(10, 18), radius="$md", contenido centrado, font_weight=600,
press_scale=0.96.

card(**estilo)¶
Columna con superficie, borde sutil, sombra y esquinas grandes. Por defecto: bg="$surface",
radius="$lg", padding=20, gap=12, border_width=1, border_color="$border",
shadow=(0, 10, 30, "$shadow").
card(width=320)[
text("Copias de seguridad", size=16, weight=600),
text("La última terminó hace 12 minutos sin errores.", color="$muted"),
]

badge(label, *, tone="accent")¶
Píldora compacta de texto. tone es el nombre de un token de color del tema.
[
badge("beta"),
badge("Estable", tone="success"),
badge("Caído", tone="danger"),
badge("v0.5", tone="primary"),
]

stat(label, value, delta=None, *, positive=True)¶
Tarjeta de métrica: etiqueta, valor grande y variación opcional en verde (positive=True) o rojo.
Tiene grow=1, así que varias stat en una row se reparten el ancho.
row(gap=16)[
stat("INGRESOS", "48.2k €", "+12%"),
stat("DEVOLUCIONES", "2,1%", "+0,4", positive=False),
]

progress(value, *, max=1.0, color="$primary", **estilo)¶
Barra de progreso: value va de 0 a max (por defecto, una fracción de 0 a 1) y se recorta a ese
rango. Al cambiar el valor, la barra crece hasta su nuevo sitio (con App(motion=False), de
golpe). color admite un color del tema o un gradiente (desde, hasta, ángulo); los estilo van
al carril (height, width…).
column(gap=10, width=260)[
progress(0.35),
progress(0.7, color=("$primary", "$accent", 0)),
progress(3, max=10, height=6, color="$success"),
]

spinner(*, size=8, gap=5, tone="$primary", key=None, **estilo)¶
Indicador de carga: tres puntos que laten en cadena (pulse: un vaivén perpetuo de opacidad).
Mientras está visible el movimiento no se detiene — es lo que necesita un indicador — así que
retíralo cuando la carga termine. Con App(motion=False) queda fijo, a opacidad plena.
# El spinner no reposa nunca: para la captura, la app va con el movimiento apagado.
App(
lambda: box(width=160, height=64, align="center", justify="center")[spinner()],
size=(200, 100),
motion=False,
).run()

skeleton(*, lines=1, width="100%", height=14, gap=8, **estilo)¶
Marcador de carga: bloques con la forma del contenido que está por llegar. Con varias lines, la
última queda más corta, como el final de un párrafo. Late despacio (pulse) mientras está
visible. Los estilo van a cada bloque (bg, radius…).
App(
lambda: column(width=280, gap=14)[skeleton(lines=2, height=12), skeleton(height=12, width=100)],
size=(280, 140),
motion=False,
).run()

divider(*, vertical=False, color="$border", **estilo)¶
Línea de 1 px. Horizontal por defecto (ocupa el ancho de su columna); con vertical=True, para
separar elementos de una row (ocupa su alto, o el height que le des).
avatar(name="", *, src=None, size=32, **estilo)¶
Círculo de size px. Con src (ruta o bytes) muestra la imagen recortada (fit="cover"); sin
ella, las iniciales de name (hasta dos) sobre un color del tema que depende del nombre, así que
una persona siempre tiene el mismo color.

checkbox(checked, label=None, *, on_change=None, key=None, **estilo) y switch(on, label=None, ...)¶
Casilla de verificación e interruptor. Con un Signal[bool] se enlazan solos: un clic (o Enter /
Espacio con el foco) lo alterna. on_change(valor) se llama en cada cambio; con un bool fijo y
on_change, gestionas el valor tú. Toda la fila (control y etiqueta) es la zona de clic.
dark = Signal(True)
accepted = Signal(False)
column(gap=12)[
switch(dark, "Tema oscuro", on_change=lambda on: theme.set(MIDNIGHT if on else DAYLIGHT)),
checkbox(accepted, "Acepto las condiciones"),
]

radio(options, selected, *, key=None, **estilo)¶
Grupo de botones de opción: uno activo a la vez. options son valores (o pares (valor, etiqueta))
y selected, el Signal con el valor activo. Cada fila (círculo y etiqueta) se activa con clic,
Enter o Espacio y tiene la key f"{key}-{valor}". El punto interior entra encogiéndose.
plan = Signal("pro")
radio([("free", "Gratis"), ("pro", "Pro"), ("team", "Equipo")], plan, key="plan")

tabs(options, selected, *, key=None, **estilo)¶
Pestañas con indicador bajo la activa. options es una lista de valores o de pares
(valor, etiqueta) y selected el Signal con el valor activo. Con una pestaña enfocada, ← y →
cambian de pestaña. Cada pestaña tiene la key f"{key}-{valor}".
page = Signal("resumen")
tabs([("resumen", "Resumen"), ("tablero", "Tablero"), ("equipo", "Equipo")], page, key="nav")

segmented(options, selected, *, width=220, key=None, **estilo)¶
Selector segmentado: el pulgar se desliza hasta la opción activa. options son valores (o pares
(valor, etiqueta)) y selected, el Signal con el valor activo. Las ranuras miden todas lo
mismo, de ahí el width en píxeles (es lo que permite anclar el pulgar por fracción y que se
deslice). Cada opción tiene la key f"{key}-{valor}" y el pulgar la suya, f"{key}-thumb". Los
estilo van al contenedor (bg…).

slider(value, *, min=0.0, max=1.0, width=180, on_change=None, key=None, **estilo)¶
Deslizador: arrastra el pulgar para cambiar value (un Signal[float]), recortado al rango
min…max. width fija el ancho en píxeles (el arrastre se calcula con él: no lo pongas en %).
on_change(valor) se llama en cada paso del arrastre. El relleno y el pulgar siguen el valor con
la animación común; con App(motion=False), al instante.

pages(current, views, *, key="pages", **estilo)¶
Una vista por valor de current (un Signal), con transición al cambiar: la página nueva entra
fundiéndose y la anterior se va. views asocia cada valor con una función que devuelve su
contenido; un valor sin vista lanza ValueError. Es el compañero natural de tabs: comparten el
Signal, cambiar de pestaña cambia de página.
Cada página es su propio scroll (su key es f"{key}-{valor}") que ocupa el espacio libre, así
que no hace falta darle height; los estilo van a él (padding, gap…). Al volver a una
página empieza arriba.
pages(
current,
{
"uno": lambda: column(gap=6)[text("Página uno"), text("Su contenido", color="$muted")],
"dos": lambda: column(gap=6)[text("Página dos"), text("Otro contenido", color="$muted")],
},
key="demo",
padding=8,
height=90,
)

select(value, options, *, placeholder="Elegir…", key=None, **estilo)¶
Desplegable. value es un Signal y options, valores o pares (valor, etiqueta). La lista se
abre por encima de todo (layer="overlay"), con scroll si es larga, y se cierra al elegir, al hacer
clic fuera, con Esc o con Enter. Con el desplegable enfocado, ↑ y ↓ cambian el valor sin abrirlo.
Ancho mínimo de 160 px; los estilos extra (width, grow…) se aplican al contenedor. Las opciones
tienen la key f"{key}-opt-{índice}".

combobox(value, options, *, placeholder="Buscar…", empty="Sin resultados", on_change=None, key=None, **estilo)¶
Como select, pero se escribe para buscar: la lista se abre al escribir (o con ↓, o con la flecha)
y muestra las opciones cuya etiqueta contiene el texto, sin distinguir mayúsculas ni tildes
(«av» encuentra «Ávila»). ↑/↓ recorren las coincidencias, Enter elige la resaltada y Esc cierra sin
cambiar nada; al cerrar, el campo vuelve a mostrar la opción elegida. empty es lo que se lee
cuando nada coincide. on_change(valor) se llama al elegir. El campo tiene la key f"{key}-field"
y las opciones f"{key}-opt-{índice}", con el índice en options (no en la lista filtrada).
Útil cuando hay demasiadas opciones para un select.
from aurora import App, Signal, column, combobox
ciudades = ["Albacete", "Alicante", "Ávila", "Barcelona", "Málaga", "Valencia", "Valladolid"]
ciudad = Signal("Valencia")
def view():
return column(padding=20)[combobox(ciudad, ciudades, key="ciudad", width=260)]
App(view, size=(420, 300)).run()

date_picker(value, *, min=None, max=None, placeholder="Elegir fecha", format=..., first_weekday=0, on_change=None, key=None, **estilo)¶
Selector de fecha: un desplegable con el calendario del mes. value es un Signal con un
datetime.date (o None sin elegir). Se abre en el mes de la fecha elegida; las flechas de la
cabecera cambian de mes y un clic en un día lo elige y cierra. Las fechas fuera de [min, max] se
ven atenuadas y no se pueden elegir (ni navegar a meses fuera del rango). El día de hoy va marcado
y «Hoy» lo elige.
Con el calendario abierto, ←/→ mueven un día, ↑/↓ una semana y RePág/AvPág un mes (el mes visible
sigue al valor); Enter o Esc lo cierran. format recibe la fecha y devuelve el texto del campo
(por defecto 19 oct 2026), y first_weekday es el primer día de la semana (0 = lunes,
6 = domingo). Los días tienen la key f"{key}-day-{fecha.isoformat()}" y se anuncian completos a
los lectores de pantalla («lunes, 19 de octubre de 2026»).
from datetime import date
from aurora import App, Signal, column, date_picker
entrega = Signal(date(2026, 10, 19))
def view():
return column(padding=20)[date_picker(entrega, min=date(2026, 10, 5), key="entrega", width=220)]
App(view, size=(340, 440)).run()

number_input(value, *, min=None, max=None, step=1, decimals=None, on_change=None, key=None, **estilo)¶
Campo numérico: se escribe el número o se ajusta con ↑/↓ y con las flechas de la derecha (que se
atenúan en los límites). value se recorta a [min, max]. Con step entero y un valor entero el
campo trabaja con int; si no, con float redondeado a decimals (por defecto, los decimales de
step: 0.25 → 2). La coma vale como separador decimal.
Mientras se escribe, cada número válido y dentro del rango se asigna al momento; un texto a medias
("-", "1,") o fuera del rango espera, y al salir del campo o pulsar Enter se recorta al rango o
vuelve al último valor válido. on_change(valor) se llama con cada valor nuevo. Ancho por defecto
de 140 px; el campo tiene la key f"{key}-field" y es un spinbutton para los lectores.
cantidad = Signal(3)
precio = Signal(12.5)
number_input(cantidad, min=1, max=99, key="cantidad"),
number_input(precio, step=0.5, key="precio"),

virtual_list(count, item, *, item_height, key, gap=0, overscan=4, **estilo)¶
Lista desplazable de count filas que solo construye las visibles: con 100 000 filas, cada
reconstrucción crea unas pocas decenas y desplazarse cuesta lo mismo que con 20.
@component
def fila(i: int, seleccionada: bool):
return row(grow=1, padding=(0, 12), align="center", bg="$surface" if seleccionada else None)[
text(tareas()[i].titulo)
]
virtual_list(len(tareas()), lambda i: fila(i, i == elegida()), item_height=36, key="tareas")
item(i)devuelve la filai. Cada fila ocupa un hueco de todo el ancho yitem_heightde alto (másgapentre filas): dalegrow=1a su raíz para que lo rellene. Todas las filas miden lo mismo; así la lista conoce su alto total sin construirlas.- Se comporta como un
scrollvertical (los**estilovan a él) y lakey, obligatoria, guarda su posición y su zona visible. - Al desplazarse solo se reconstruye cuando cambia el rango de filas visibles, en el mismo frame.
overscanfilas de más por cada lado cubren el desplazamiento entre medias. - Con filas
@component, las que siguen visibles no se reconstruyen. - Solo las filas construidas existen en el árbol: Tab recorre las visibles, y
Pilot.rectde una fila fuera de la zona visible devuelveNone.
table(rows, columns, *, key, sort=None, widths=None, on_row_click=None, row_height=36, header_height=40, striped=True, empty="Sin datos", overscan=4, **estilo)¶
Tabla de datos con cabecera fija, ordenación, columnas redimensionables y filas virtualizadas: solo se construyen las visibles, así que con 100 000 filas desplazarse cuesta lo mismo que con 20.
from aurora import Column, table
table(
pedidos(),
[
Column("id", "Nº", width=80, align="end"),
Column("cliente", "Cliente", width=220),
Column("total", "Total", align="end", format=lambda v: f"{v:,.2f} €"),
Column("estado", "Estado", cell=lambda p: badge(p.estado), sortable=False),
],
key="pedidos",
on_row_click=lambda p: abierto.set(p.id),
)

rowses cualquier secuencia: dicts, tuplas, dataclasses u otros objetos.columnsson objetosColumno simplemente keys (["id", "nombre"]).- Ordenar: pulsar una cabecera ordena por esa columna, ascendente → descendente → orden
original. Los textos se comparan sin distinguir mayúsculas, los vacíos (
None) van siempre al final y los empates conservan el orden original. El orden se calcula una vez y se memoriza mientrasrowssea el mismo objeto y no cambie de longitud: si cambian los datos, pasa una secuencia nueva en vez de modificarla en su sitio. - Redimensionar: arrastrar el borde derecho de una cabecera cambia el ancho de su columna (sin
bajar de
min_width). Si las columnas no caben, la tabla se desplaza en horizontal. - El orden y los anchos se guardan por
key. Para leerlos o fijarlos desde fuera, pasa tusSignal:sortcon(key_de_columna, descendente)oNone, ywidthscon{key_de_columna: ancho}(las que falten usanColumn.width). on_row_click(fila)hace las filas pulsables.stripedalterna el fondo de las filas yemptyes el texto que se muestra sin filas.- Sin
heightnimax_height, ocupa el espacio libre (grow=1), como unscroll. - En los tests, las cabeceras tienen key
"{key}/h/{columna}", los bordes"{key}/resize/{columna}", las filas"{key}/row/{índice en rows}"y el cuerpo desplazable"{key}/body".
Column(key, title=None, *, width=160, min_width=48, align="start", value=None, format=None, cell=None, sortable=True, resizable=True)¶
| Campo | Descripción |
|---|---|
key |
Identifica la columna. Sin value, el valor de cada celda es fila[key] en un dict, getattr(fila, key) en un objeto o la posición de la columna en una tupla o lista. |
title |
Texto de la cabecera. Por defecto, la key. |
width / min_width |
Ancho inicial y mínimo al redimensionar, en píxeles. |
align |
"start", "center" o "end" (números). |
value |
value(fila): valor de la celda, que es lo que se ordena. |
format |
format(valor): texto que se muestra. Por defecto, str(valor) (None se muestra vacío). |
cell |
cell(fila): elemento que sustituye al texto (badges, avatares, botones). La ordenación sigue usando el valor. |
sortable / resizable |
Si la cabecera ordena al pulsarla / si su borde se puede arrastrar. |
tooltip(child, tip)¶
Muestra tip en una burbuja al dejar el puntero medio segundo sobre child. Aparece debajo
(o encima, si no cabe), sin salirse de la ventana, se oculta al hacer clic o pulsar una tecla, y
no intercepta el ratón. Es lo mismo que pasar tooltip="..." a cualquier elemento:
tooltip(button("Guardar", save), "Ctrl+S")
image("info.svg", width=16, tooltip="Se actualiza cada hora")
La burbuja usa los colores heredables tooltip_bg y tooltip_color, que por defecto invierten el
tema ($text sobre $background), así destaca en claro y en oscuro.
dialog(open, content, *, title=None, on_close=None, width=420, key=None, **estilo)¶
Diálogo modal: fondo oscurecido y tarjeta centrada con content. Con un Signal[bool] en open
se cierra solo — el clic fuera y Esc lo ponen a False —; on_close se llama en cada cierre, útil
si open es un booleano fijo o quieres cerrar un modal de varios estados
(on_close=lambda: modal.set(None)). title añade un encabezado; los estilo van a la tarjeta
(padding, shadow…).
La tarjeta toma el foco al abrirse y lo atrapa (Tab no sale de ella), salvo que el contenido traiga
un autofocus, que gana. Entra con un zoom suave y sale fundiéndose. Devuelve None mientras está
cerrado: colócalo como último hijo de la raíz (o con layer="overlay").
box(width=380, height=260, bg="$background")[
dialog(
open_,
column(gap=14)[
text("¿Eliminar la tarea «Refactor»?"),
row(gap=8, justify="end")[
button("Cancelar", lambda: None, variant="soft"),
button("Eliminar", lambda: None),
],
],
title="Confirmación",
width=300,
)
]

toast(message, *, on_close=None, tone="$accent", key=None, **estilo)¶
Aviso flotante abajo a la derecha. message puede ser un Signal[str | None]: se muestra si no es
None y un clic lo descarta (el Signal pasa a None). on_close se llama en cada cierre; tone
es el color del punto de acento y los estilo van al aviso (right, bottom, max_width…). Entra
deslizándose desde abajo y sale fundiéndose. Devuelve None mientras no hay mensaje: colócalo como
último hijo de la raíz. Para que se vaya solo, programa el Signal con la app
(app.spawn(asyncio.sleep(3)) y ponerlo a None al terminar).
box(width=380, height=200, bg="$background", padding=18)[
column(gap=6)[text("Ajustes guardados", weight=600), text("Hace un momento", color="$muted", size=12)],
toast("Cambios guardados", tone="$success"),
]

menu(trigger, items, *, key, drop=44, **estilo)¶
Menú de acciones: trigger es el elemento que se pulsa (normalmente un botón o un "⋯") y items
la lista de opciones, (etiqueta, on_click) o (etiqueta, on_click, estilo) — el estilo extra va
al texto, para una acción peligrosa: {"color": "$danger"}. La lista se pinta por encima
(layer="overlay"), entra deslizándose y se cierra al elegir, al hacer clic fuera o con Esc.
drop es la distancia del panel al borde superior del disparador y key (obligatoria, como en
virtual_list) guarda el estado de abierto: las opciones tienen la key
f"{key}-item-{índice}".
menu(
button("Acciones", variant="soft"),
[
("Editar", edit),
("Duplicar", duplicate),
("Eliminar", remove, {"color": "$danger"}),
],
key="acciones",
)
accordion(sections, open, *, key=None, **estilo)¶
Acordeón: cada sección se despliega al pulsar su cabecera. sections son triples
(valor, título, contenido) y open, el Signal con la sección desplegada (None con todas
plegadas; pulsar la cabecera abierta la pliega). El contenido se despliega con la altura animada y
overflow="hidden", se queda montado al plegarse (conserva su estado y no recibe clics
mientras está oculto). Cada sección tiene la key f"{key}-{valor}".
accordion(
[
("uno", "Cómo funciona", text("El contenido se recorta y se despliega animado.")),
("dos", "Atajos", text("Pulsa cualquier cabecera para abrir o cerrar su sección.")),
],
section,
key="demo",
width=380,
)

field(control, *, label=None, hint=None, error=None, key=None, **estilo)¶
Campo de formulario: etiqueta encima, el control y, debajo, un mensaje de ayuda o de error. error
sustituye a hint en color de peligro ($danger): pásalo con el texto del problema y quítalo
cuando el valor sea válido. Los estilo van a la columna (gap, width, grow…).
email = Signal("")
field(text_input(email, placeholder="tu@correo.com", width=240), label="Correo", hint="No lo compartimos")

text_input(value, *, placeholder="", on_change=None, on_submit=None, key=None, **estilo)¶
Campo de texto de una línea. value es normalmente un Signal[str], y entonces queda enlazado
en los dos sentidos: lo que se escribe actualiza el Signal y lo que asignes al Signal aparece en
el campo (por ejemplo, vaciarlo al enviar).

| Parámetro | Descripción |
|---|---|
value |
Signal[str] (enlazado), o un str/callable con on_change para gestionar el valor tú. Sin on_change, un valor fijo no cambia al escribir. |
placeholder |
Texto atenuado que se muestra con el campo vacío. Se toma literalmente (un "$" inicial no es un token). |
on_change |
fn(texto) en cada cambio (después de actualizar el Signal). |
on_submit |
fn(texto) al pulsar Enter. |
Por defecto: bg="$surface", borde de 1 px $border, radius="$md", padding=(9, 12) y
200 px de ancho si su contenedor no le da otro (con align="stretch", ocupa el ancho disponible).
Un campo con foco siempre muestra el anillo de foco, también tras un clic.
Edición: escribir (incluido texto compuesto con IME), Retroceso/Supr (con Ctrl, o Alt en macOS,
por palabras), flechas (con Mayús seleccionan; con Ctrl/Alt saltan palabras), Inicio/Fin,
Ctrl+A, Ctrl+C/X/V, Ctrl+Z y Ctrl+Mayús+Z o Ctrl+Y (Cmd en macOS). Con el ratón: clic coloca el
cursor (Mayús+clic amplía la selección), arrastrar selecciona, doble clic selecciona una palabra y
triple clic todo. El texto se recorre por grafemas: un emoji compuesto o una letra con tilde
combinada se borran de una vez. Tab sale del campo; las teclas pasan antes por los on_key.
textarea(value, *, placeholder="", rows=3, on_change=None, on_submit=None, key=None, **estilo)¶
Como text_input, pero multilínea: Enter inserta un salto de línea y Ctrl+Enter (Cmd+Enter en
macOS) llama a on_submit. Las líneas largas se parten solas, y el campo crece con el contenido
a partir de un mínimo de rows líneas. Las flechas ↑ ↓ cambian de línea e Inicio/Fin van al
principio y al final de la línea. Para limitar su altura, ponlo dentro de un
scroll(max_height=...): el cursor siempre se mantiene a la vista.
Animaciones¶
Las animaciones se declaran en el elemento y las interpola el núcleo en cada frame, sin volver a llamar a Python ni competir por el GIL. Cinco propiedades de estilo las activan:
from aurora import animate
row(
key=f"tarea-{t.id}", # identidad estable: imprescindible para animar
bg="$surface_pressed" if t.done else "$surface",
transition=animate.spring(), # funde bg, border_color, color, opacity y radius
animate_layout=True, # se desliza cuando el layout lo mueve
enter=animate.slide(y=12), # aparece subiendo y haciéndose visible
exit=animate.zoom(0.95), # se va encogiendo y desvaneciéndose
)
box(pulse=animate.pulse()) # late sin parar (indicadores de carga)
| Propiedad | Valores | Descripción |
|---|---|---|
transition |
animate.spring(...), animate.tween(...) |
Al reconstruir la vista, los cambios de bg, border_color, color (el del texto del propio nodo), opacity y radius se animan desde lo que se ve hacia el valor nuevo. Un color que aparece o desaparece se funde desde/hacia transparente. Es también la curva por defecto de las otras tres. El color heredado de un contenedor no se anima a través de los hijos: para eso, pon transition en el propio texto. |
animate_layout |
True o una curva |
Cuando un layout cambia la caja del elemento (otro elemento aparece, se va, cambia de tamaño o se reordena), se desliza desde la caja que mostraba; su fondo, borde y sombra cambian de tamaño con ella y sus hijos se desplazan con él. Con True usa la curva de transition (o un muelle). Al redimensionar la ventana el layout salta, sin animarse. |
enter |
efecto | Valores desde los que aparece cuando entra en la vista. No se reproduce en el primer frame de la app (ni en render_png). |
exit |
efecto | Valores hacia los que se va cuando sale de la vista. Mientras dura se pinta una instantánea del elemento tal como estaba, encima de su capa: ya no ocupa sitio en el layout (los demás pueden deslizarse a su hueco con animate_layout) ni recibe eventos. Si vuelve a la vista antes de terminar, entra desde donde estaba. |
pulse |
animate.pulse(...) |
Vaivén perpetuo de la opacidad (1 → floor → 1), para indicadores de carga. Nunca termina: mientras está visible, la app sigue pintando frames. |
Todo se anima por key: un elemento sin key explícita toma la identidad de su posición, así
que en listas que cambian dale siempre una. Mientras una animación de layout o un efecto
transforma un elemento, los clics usan su caja final (la de pilot.rect).
Movimiento por defecto en los componentes¶
No hace falta declarar nada para que los componentes se sientan vivos: switch, checkbox,
tabs, select, radio, segmented y slider ya traen sus animaciones con una escala común
de duraciones cortas (el knob de un switch se desliza, la marca de una casilla entra encogiéndose,
el subrayado de las pestañas funde, el menú de un select aparece deslizándose, el pulgar de un
segmented se desliza hasta la opción activa y las barras de progress crecen hasta su valor).
spinner y skeleton, en cambio, laten con un vaivén perpetuo (pulse): mientras están
visibles la app sigue pintando frames y pilot.settle() agota sus frames sin llegar al reposo.
Si prefieres que nada se mueva —tests de snapshot, equipos que lo piden—, apágalo entero:
Los efectos de entrada no se reproducen en el primer frame ni en render_png (y el vaivén nace a
opacidad plena): las capturas y la documentación siguen siendo deterministas.
Curvas de tiempo¶
| Función | Descripción |
|---|---|
animate.spring(*, stiffness=380, damping=30, mass=1, delay=0) |
Muelle físico (por defecto): más stiffness, más rápido; menos damping, más rebote. Su duración sale de la física (~0,3 s con los valores por defecto) y, si el destino cambia a medias, sigue desde donde está. |
animate.tween(duration=0.25, *, easing="ease_out", delay=0) |
Duración fija en segundos con una curva de CSS: "linear", "ease", "ease_in", "ease_out", "ease_in_out". |
delay (segundos) retrasa el comienzo; con él se escalonan las entradas de una lista
(enter=animate.slide(y=8, transition=animate.spring(delay=i * 0.03))).
Vaivén perpetuo¶
animate.pulse(duration=1.0, *, delay=0, floor=0.35) baja la opacidad de 1 a floor y la sube
otra vez, sin terminar nunca. Se pasa en la propiedad pulse de un elemento (no en
transition) y es para indicadores de carga: delay escalona varios elementos y floor marca la
opacidad mínima.
Efectos de entrada y salida¶
Un efecto son cuatro valores relativos al elemento: opacidad, desplazamiento x/y en píxeles y
escala alrededor de su centro. Cada uno acepta transition= para usar otra curva que la del
elemento.
| Función | Efecto |
|---|---|
animate.fade(opacity=0.0) |
Solo opacidad. |
animate.slide(x=0, y=0, *, fade=True) |
Desplazamiento, por defecto desvaneciéndose. |
animate.zoom(scale=0.9, *, fade=True) |
Escala, por defecto desvaneciéndose. |
animate.effect(*, opacity=1, x=0, y=0, scale=1) |
Cualquier combinación. |
Los valores inválidos (rigidez no positiva, duración o retardo negativos, easing desconocido,
escala negativa) lanzan ValueError al crearlos.
En los tests, pilot.frame(dt) avanza las animaciones dt segundos y devuelve True mientras
alguna sigue en curso; pilot.settle() espera a que terminen todas (salidas incluidas).
Propiedades de estilo¶
Las longitudes están en píxeles lógicos. Las propiedades marcadas con H se heredan de los contenedores (como en CSS).
Layout¶
| Propiedad | Valores | Por defecto | Descripción |
|---|---|---|---|
direction |
"row", "column" |
"column" |
Eje principal. |
gap |
número | 0 |
Espacio entre hijos. |
padding |
número · (v, h) · (t, r, b, l) |
0 |
Relleno interior. |
align |
"start", "center", "end", "baseline", "stretch" |
"stretch" |
Alineación en el eje cruzado. "baseline" alinea textos de distinto tamaño por su línea base. |
justify |
"start", "center", "end", "between", "around", "evenly" |
"start" |
Distribución en el eje principal. |
width, height |
número · "50%" |
automático | Tamaño fijo o relativo al contenedor. |
min_width, max_width |
número | — | Límites de ancho. |
min_height, max_height |
número | — | Límites de alto. max_height en un scroll limita cuánto crece antes de desplazarse. |
grow |
número | 0 |
Cuánto crece para ocupar espacio libre. |
shrink |
número | 1 |
Cuánto se encoge si falta espacio. |
wrap |
bool |
False |
Permite saltar de línea a los hijos (rejillas de tarjetas). |
position |
"absolute" |
relativo | Saca al elemento del flujo; se coloca con top/right/bottom/left respecto a su padre. Útil para modales, toasts y superposiciones. |
top, right, bottom, left |
número · "50%" |
— | Desplazamientos para position="absolute". Los porcentajes se resuelven contra la caja de relleno del padre (como la posición absoluta de CSS). |
overflow |
"visible", "hidden" |
"visible" |
Con "hidden" el contenido que se sale de la caja se recorta (pintado y clics) sin tocar el layout. Los hijos se encogen para caber salvo que les des shrink=0: con él desbordan y es el recorte quien los oculta (así despliega el accordion). |
src |
ruta o bytes |
— | Origen de una imagen. Usa la primitiva image(). |
fit |
"contain", "cover", "fill", "none" |
"contain" |
Encaje de una imagen en su caja. |
scroll |
"x", "y", "both" |
— | Convierte la caja en contenedor desplazable. Usa la primitiva scroll(), que añade buenos valores por defecto. |
Truco: columnas de igual ancho.
grow=1, width=0en cada hijo de unarowreparte el espacio a partes iguales, sin importar su contenido.
Apariencia¶
| Propiedad | Valores | Descripción |
|---|---|---|
bg |
color | Color de fondo. |
bg_hover |
color | Fondo con el cursor encima (requiere on_click u on_drag). Transición animada. |
bg_pressed |
color | Fondo mientras se mantiene pulsado. Transición animada. |
gradient |
(desde, hasta, ángulo) |
Gradiente lineal. Ángulo en grados: 0 = izquierda→derecha, 90 = arriba→abajo. Reemplaza a bg. |
radius |
número | Radio de las esquinas. Usa 999 (o "$full") para píldoras y círculos. |
border_width |
número | Grosor del borde (también reserva espacio en el layout). |
border_color |
color | Color del borde. |
shadow |
(dx, dy, blur, color) |
Sombra difuminada desplazada. |
opacity |
0.0 – 1.0 |
Opacidad del elemento y todos sus hijos. |
press_scale |
número | Escala al pulsar (p. ej. 0.96). Animada. |
transition, animate_layout, enter, exit, pulse |
curvas y efectos de animate |
Animaciones declarativas: transition funde los cambios de bg, border_color, color, opacity y radius; pulse (un animate.pulse()) hace latir la opacidad sin parar. Ver Animaciones. |
scrollbar_color |
color | Color del pulgar de la barra de un scroll (se pinta translúcido). scroll() usa $muted. |
placeholder_color |
color | Color del placeholder de un campo de texto. Por defecto, el color del texto al 45 %. |
selection_color |
color | Fondo del texto seleccionado. Por defecto, focus_color translúcido. |
tooltip |
texto | Texto de ayuda que aparece al dejar el puntero encima. Ver tooltip. |
cursor |
"default", "pointer", "text", "grab", "grabbing", "col-resize", "row-resize", "crosshair" |
Cursor del ratón sobre el elemento y sus hijos (salvo que un hijo fije otro). Durante un on_drag se mantiene el del elemento arrastrado. |
capture |
bool |
Barrera de eventos: los clics dentro no llegan a los ancestros. Úsalo en el contenido de un modal. |
layer |
"base", "overlay" |
Con "overlay", el elemento se maqueta en su sitio pero se pinta y recibe eventos por encima de todo el árbol, aunque se declare antes que otros. Ideal para desplegables, tooltips y modales. Se pinta en coordenadas absolutas: no hereda la opacity ni el press_scale de sus ancestros. Los clics siguen burbujeando por sus ancestros del árbol. |
Texto¶
| Propiedad | Alias | Valores | Por defecto | |
|---|---|---|---|---|
font_size |
size |
número | 14 |
H |
font_weight |
weight |
100–900 |
400 |
H |
color |
— | color | $text |
H |
font_family |
family |
nombre de fuente | fuente del tema (Inter) |
H |
line_height |
— | múltiplo del tamaño (1.4) |
el de la fuente | H |
letter_spacing |
— | número (px, admite negativos) | 0 |
H |
focus_color |
— | color del anillo de foco | $primary |
H |
tooltip_bg, tooltip_color |
— | colores de la burbuja de los tooltips | $text, $background |
H |
max_lines |
— | entero | sin límite | Corta con … al superar ese número de líneas. |
text_align |
— | "left", "center", "right" |
"left" |
Alineación dentro del ancho del texto. |
Si la fuente pedida no está instalada, se usa la de reserva del sistema, y los glifos que falten (emoji, símbolos) se buscan automáticamente en otras fuentes.
Temas y tokens¶
En cualquier propiedad que acepte color o radio puedes usar un token: un string que empieza
por $ y se resuelve con el tema activo.
Tokens incluidos¶
| Colores | Uso |
|---|---|
$background |
Fondo de la ventana |
$surface, $surface_hover, $surface_pressed |
Tarjetas, paneles y sus estados |
$border |
Bordes sutiles |
$text, $muted |
Texto principal y secundario |
$primary, $primary_hover, $primary_pressed |
Color de marca y sus estados |
$accent, $success, $danger |
Acentos y semántica |
$shadow, $glow |
Sombras neutras y brillo de marca |
| Radios | |
|---|---|
$sm 8 · $md 12 · $lg 18 · $full 999 |
Temas incluidos y temas propios¶
MIDNIGHT (oscuro, por defecto) y DAYLIGHT (claro) definen todos los tokens. Para crear el tuyo:
from aurora import MIDNIGHT, Theme
OCEAN = Theme(
colors={
**MIDNIGHT.colors,
"primary": "#0EA5E9",
"primary_hover": "#38BDF8",
"primary_pressed": "#0284C7",
"glow": "#0EA5E940",
},
radius=MIDNIGHT.radius,
font_family="Inter",
)
Un token que no existe en el tema lanza KeyError al construir la vista, para que los errores
de escritura se detecten enseguida.
Formatos de valores¶
| Tipo | Formato |
|---|---|
| Color | "#rgb", "#rrggbb" o "#rrggbbaa" (el último par es la opacidad: "#00000080" = negro al 50 %). También tokens "$nombre". |
| Longitud | Número en píxeles lógicos, o "NN%" en width/height. |
| Ángulo | Grados (float). |
Un color mal escrito lanza ValueError con el valor problemático en el mensaje.
Empaquetado: aurora build¶
aurora build # empaqueta main.py (o app.py) en dist/
aurora build app.py --name "Mi App" --icon assets/icon.ico --include assets
Convierte la app en un ejecutable distribuible que no necesita Python instalado: un solo
archivo (dist/main.exe en Windows, dist/main en Linux) o, en macOS, una dist/main.app.
Por debajo usa PyInstaller, que es una dependencia opcional:
Se empaqueta para el sistema en el que se ejecuta (para Windows, macOS y Linux hay que construir
en cada uno; en CI, una matriz). También funciona como python -m aurora build.
Opciones¶
Todas tienen su equivalente en [tool.aurora.build] de pyproject.toml; las de la línea de
comandos tienen prioridad, salvo --include, que se suma a include.
| Opción | pyproject.toml |
Por defecto | Descripción |
|---|---|---|---|
script |
script |
main.py o app.py |
Script de la app. |
--name |
name |
nombre del script | Nombre del ejecutable. |
--icon |
icon |
— | Icono: .ico en Windows, .icns en macOS. Otros formatos (PNG) se convierten si está instalado Pillow. Linux no incrusta iconos en el ejecutable. |
--include RUTA |
include (lista) |
— | Archivo o carpeta que se copia con la app, con la misma ruta relativa al script (assets/logo.png sigue siendo assets/logo.png). Lo que está fuera de la carpeta del script va a la raíz. Repetible. |
--onefile / --onedir |
onefile |
true |
Un solo archivo, o una carpeta con el ejecutable y sus bibliotecas, que arranca antes (no se descomprime en cada inicio). En macOS sin consola siempre se genera una .app. |
--console / --windowed |
console |
false |
Mantener la ventana de consola (útil para ver print y errores mientras pruebas). |
--upx / --no-upx |
upx |
false |
Comprime los binarios con UPX (que debe estar instalado): el ejecutable pesa menos, a costa de falsos positivos de antivirus y un arranque algo más lento. |
--dist |
dist |
dist |
Carpeta de salida. Los archivos intermedios van a build/. |
--clean |
— | — | Borra la caché de PyInstaller antes de empaquetar. |
UPX viene desactivado a propósito: además de lo anterior, el resultado del build no debe depender
de si la máquina tiene UPX instalado. Si lo activas y PyInstaller avisa de que algún binario no es
comprimible (python3.dll y similares), es normal: lo deja sin comprimir y el build termina igual.
[tool.aurora.build]
script = "app.py"
name = "Monitor"
icon = "assets/icon.ico"
include = ["assets"]
Las rutas de pyproject.toml son relativas a ese archivo (que se busca desde el directorio
actual hacia arriba); las de la línea de comandos, al directorio actual. Una clave desconocida,
un tipo incorrecto o una ruta que no existe se informan antes de empaquetar.
resource(path)¶
Devuelve la ruta absoluta de un recurso de la app. Una ruta relativa se resuelve desde la
carpeta del script principal, no desde el directorio de trabajo; dentro del ejecutable, desde
donde quedan los archivos de include, que conservan su estructura. Así funciona igual con
python app.py, con doble clic en el ejecutable o lanzándolo desde otra carpeta. Una ruta
absoluta se devuelve tal cual.
Avisos de obsolescencia¶
aurora.AuroraDeprecationWarning (subclase de FutureWarning) es el aviso que emite cualquier parte
de la API marcada como obsoleta: dice desde qué versión lo está, en cuál se eliminará y qué usar
en su lugar. Se ve por defecto en la consola; para que los tests de tu app fallen al usar algo
obsoleto:
Los plazos y qué cuenta como API pública están en la política de estabilidad.