Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Aura UI
Interfaces nativas aceleradas por GPU para macOS, Windows, Linux, Android e iOS con un solo código en Rust. Estilo encadenado con los nombres de Tailwind, componentes tipo shadcn, sombras por capas y animaciones, pensado para gastar poco: poca RAM, casi nada de CPU en reposo y binarios pequeños.
Un solo crate con un módulo por capa: aura_ui::core (entidades, tareas, geometría, color, animación y capa de
sistema), ::gpu (renderizador), ::view (elementos, estilo, recetas y layout), ::widgets (componentes y
estructura de pantalla), ::app (ventana y bucle) y ::ai (agentes de IA; feature ai, activa por defecto:
default-features = false la quita).
use *;
La app es una closure: no hay impl, ni AppOptions, ni set_root. ui.state("clave", valor) crea el estado
la primera vez y lo conserva; update, set y toggle devuelven manejadores que mutan y redibujan. ui.page()
ocupa la ventana y hereda el tema; column![...] y row![...] quitan los .child(...) repetidos.
API de closure
- Entrada:
run_app("Título", |ui| ...); con tamaño o tema,AppOptions::new(..).size(..).run_ui(|ui| ...). - Estado:
let nombre = ui.state("nombre", String::new());→nombre.get(),nombre.update(..),nombre.set(..),nombre.toggle(). Para valores caros,ui.state_with("app", App::default)no construye el inicial si ya existe. - Formularios:
ui.text_field("Nombre", &nombre)enlaza el campo conState<String>, conserva foco y cursor y solo sincroniza el texto cuando cambia. - Listas:
ui.list("tareas", count, px(44.0), |ui, i| ..)es virtualizada: construye solo las filas visibles y conserva el scroll. Pensada para miles de elementos sin perder respuesta. - Componentes con estado:
ui.component("panel", Panel::new, panel);paneles una funciónfn(ui: &mut Ui, state: &mut Panel, handle: State<Panel>) -> Div. Sinimpl. - Composición:
column![...],row![...],ui.page(),ui.card(),ui.section(),ui.setting_row(),ui.page_header(),ui.field(),ui.empty_state(). - Convivencia:
ui.child(handle)dibuja una vista existente mientras se migra una app por partes.
Para empezar una app nueva, copia examples/tareas.rs (69 líneas): estado, formulario y lista virtualizada, sin
impl y con los componentes del tema.
Tailwind
Los nombres son los de Tailwind en snake_case y todos son métodos de Styled: con use aura_ui::prelude::*; basta.
Cada uno solo fija un campo; no hay trabajo en tiempo de ejecución ni memoria extra hasta que se usan.
| Familia | Utilidades |
|---|---|
| Espaciado y tamaño (escala completa, 1 = 4 px) | p_4 px_2 pt_0_5 m_8 mx_auto gap_x_2 w_64 h_1_2 size_10 min_w_0 max_w_full inset_0 top_px |
| Flex | flex_row flex_col_reverse flex_wrap grow shrink_0 flex_1 flex_auto items_baseline justify_evenly self_end content_between |
| Cuadrícula | grid_cols_3 grid_rows_2 col_span_2 col_start_2 row_span_3 grid_flow_col + gap_* |
| Color | blue(500) slate(900) rose(50) del módulo palette, con .with_alpha(0.5); degradados bg_gradient_to_r(a, b) |
| Texto | text_xs … text_9xl font_thin … font_black text_center leading_tight tracking_wide truncate line_clamp_3 uppercase line_through font_mono |
| Bordes | border border_t_2 border_x_0 rounded_lg rounded_t_xl rounded_bl_full ring_2 ring_color(..) |
| Efectos | shadow_md glow opacity_50 |
| Transformaciones | rotate_45 neg_rotate_12 scale_105 scale_x_50 translate_x_4 neg_translate_y_1_2 skew_x_6 (giran y escalan desde el centro; no cambian el layout) |
| Filtros | blur_sm blur_md backdrop_blur_xl backdrop_blur_px(6.0) |
| Apilado | z_10 z_50 neg_z_10 z(7) entre hermanos, también para el ratón |
| Overflow | overflow_hidden overflow_x_scroll overflow_auto |
| Estados | hover active focus group_hover drag_over |
| Variantes | sm md lg xl xxl (según el ancho de la ventana, primero móvil), dark light |
div
.flex_col.gap_4.p_6.rounded_xl.border.border_color
.md
.dark
.child
.child
Matices
backdrop_blur_*difumina lo que hay detrás del elemento (cristal esmerilado).blur_*difumina el elemento y sus hijos, pero se aplica sobre toda su caja: también suaviza el fondo que tenga debajo y no se extiende más allá del borde.- Un desenfoque dentro de un elemento girado usa el rectángulo que envuelve al giro (no hay desenfoque girado).
- Sin GPU (el rasterizador por CPU) las transformaciones sin giro funcionan; un giro o una inclinación se pinta sin girar. Las hitboxes de un elemento transformado son el rectángulo que envuelve a la figura.
- Un desenfoque pinta el frame en una textura propia y suelta su memoria al rato de no usarse.
Todavía no existe: divide-* y space-*, cursor-* más allá de los de CursorStyle, select-none,
pointer-events-none, object-fit, mix-blend, sticky, otros filtros (brightness, grayscale…), valores
arbitrarios (w-[137px]; usa .w(px(137.0))) y las variantes disabled: y focus-within:.
Recetas
Las combinaciones que toda app repite tienen nombre y salen del tema. Son un trait, así que valen también para
Button, Badge y Style:
| Receta | Equivale a |
|---|---|
.page() |
pantalla completa, en columna, con fondo y texto del tema |
.card() |
superficie elevada, borde, rounded_xl, sombra suave y p_4 |
.panel() |
superficie plana con borde tenue (barras laterales, docks) |
.hstack() · .vstack() · .center() |
flex_row + items_center · flex_col · centrado en ambos ejes |
.muted() · .caption() |
texto secundario · texto pequeño secundario |
.heading() · .subheading() |
título de pantalla · título de sección |
.scroll_y() · .flexible() |
scroll vertical que encoge · flex_1 con min_w_0 |
Para estructura hay funciones que devuelven un Div ya compuesto:
page_header
section
setting_row
field
empty_state
Sombras y animaciones
div.shadow_lg // presets xs, sm, md, lg, xl, 2xl
div.shadow // capas
div.glow // resplandor de color
let x = cx.transition;
let t = cx.animate;
let c = cx.animate_between;
Curvas: Linear, EaseIn, EaseOut, EaseInOut, CubicBezier(..) (como CSS) y Spring { bounce }. Con retraso,
repetición y ida y vuelta. Una animación solo pide frames mientras corre.
Eficiencia
Medido en macOS arm64 con el ejemplo contador, build release:
| Memoria (huella) | 25 MB |
| CPU en reposo | 0,00 s en 20 s |
| Binario | 7,0 MB (macOS) · 6,1 MB (iOS) · 4,2 MB (iOS con opt-level = "s" y LTO completo) |
Cómo lo logra: el bucle duerme hasta que hay entrada, un temporizador o una animación; no pinta si no se ve; usa la GPU
de bajo consumo con un solo frame en cola; suelta cachés al pasar a segundo plano o ante un aviso de poca memoria; las
sombras son una sola primitiva analítica en el shader, sin pasadas de desenfoque. La API de closure añade solo un
mapa de estado por app (FxHashMap, sin asignaciones por frame) y las listas virtualizadas; no hay hilo, canal ni
capa de diffing encima del motor. Los atajos de los widgets se registran una vez al crear la ventana.
Perfil recomendado para tus apps móviles:
[]
= "s"
= "fat"
= 1
= "abort"
= "symbols"
Menos código repetido
Todo viene de use aura_ui::prelude::*;, sin importar nada más.
Lo que la librería hace sola: la vista raíz ocupa el ancho y el área segura y el tema pinta el fondo y el color de
texto por defecto (.page() la estira también al alto); los atajos de los widgets (TextInput, Picker, menús) ya
están registrados; y cualquier cambio hecho dentro de un manejador redibuja (el update de la entidad marca la
ventana), así que un listener no necesita cx.notify(). Las recetas (.page(), .card(), .hstack()…) y las
piezas de estructura (section, setting_row…) quitan las cadenas de estilo que se copiaban de una pantalla a otra.
// Clics: sin `|this: &mut V, _: &ClickEvent, cx|` ni `cx.notify()` (se avisa solo)
.on_click
.on_click // invierte un bool
.on_click // asigna un valor
.on_click // emite un evento a quien se suscriba
// Segundo plano: sin hilo propio, canal ni `sender.update`; vuelve a la vista y redibuja
cx.job;
self.search.run; // `Latest`: solo vale la última
cx.job_blocking;
// Configuración: JSON en ~/.config/<app> (APPDATA en Windows), escritura atómica
let store = new.unwrap;
let theme = store.load.str_or.to_owned;
store.update?;
// Controles y animación que toda app acaba copiando
div.pressable // mano + hover + active
tool_button
chip // etiqueta pulsable
switch
let t = cx.phase; // segundos; `wave(t, 2.0, 0.0)` da una onda de 0 a 1
let a = cx.fade_in;
// Arranque: la vista directa, sin cierre ni `set_root`
new.size.run?;
Por qué cuesta poco: cx.job reutiliza el pool de la app (en vez de un hilo del sistema por tarea) y un trabajo
cancelado no llega a tocar la UI; guardar es un temporal más un renombrado; nada de esto despierta el bucle
cuando no hay trabajo.
Migración a la API de closure
La forma nueva es run_app + ui.state, sin impl:
- Entrada:
run_app("Título", |ui| ...)oAppOptions::new(..).run_ui(|ui| ...). - Estado:
ui.state("clave", valor);update,setytogglemutan y redibujan. - Formularios:
ui.text_field("Nombre", &estado)enlaza el campo y conserva foco/cursor. - Componentes:
ui.component("panel", Panel::new, panel)para separar una app en funciones con estado propio. - Macros:
column![...]yrow![...]sustituyen las cadenas de.child(...).
Para apps que ya tienen estado en entidades (Handle/ViewContext) existe el puente
fn panel_view(this: &mut Panel, cx: &mut ViewContext<Panel>) -> AnyElement { … } +
aura_ui::component!(Panel, panel_view): tampoco se escribe impl, y las apps pueden migrarse después a
ui.component. Los traits públicos View y Render ya no existen; component! es la vía de compatibilidad
para las vistas con entidades. Los atajos de TextInput, Picker y menús ya se registran al crear la ventana.
En una aplicación que inicializa entidades o atajos sobre Window, window.set_ui("Mi app", |ui| ...)
monta la raíz de closure sin cambiar esa inicialización. Usa ui.child(handle) para dibujar el componente
existente y ui.state_with para inicializar nuevos estados una sola vez.
Los cambios de la versión 0.3.5 y las consideraciones de migración están en CHANGELOG.md.
Móvil
Ver docs/movil.md del repositorio. Resumen: iOS compila para aarch64-apple-ios y el simulador; Android
para aarch64-linux-android con NativeActivity. Toque, deslizar con inercia, pulsación larga, teclado del sistema y
área segura están implementados. En iOS, añade el manifiesto UIScene al Info.plist (plantilla en docs/ios/Info.plist;
lo exige el SDK de iOS 27). Probado en el simulador de iPhone; aún no en un iPhone ni un Android físicos.
Licencia
Aura UI tiene doble licencia, a tu elección (crates.io muestra la primera; la segunda va incluida en el paquete):
- AGPL-3.0-only (
LICENSE-AGPL, https://www.gnu.org/licenses/agpl-3.0.html): gratis. Si la usas, tu app completa debe publicarse bajo AGPL-3.0 con su código fuente. - Licencia comercial (
LICENSE-COMMERCIAL.md): para apps de código cerrado. Regalías del 10 % de los ingresos brutos de cada producto que use Aura UI. Para obtenerla escribe a camiloghs1@gmail.com.
Si no tienes la licencia comercial, aplica la AGPL-3.0. Los iconos de src/widgets/svg son de Lucide (ISC), con su
aviso en src/widgets/svg/LICENSE-lucide.
Copyright (C) 2026 Camilo Grajales Holguín.