Tutorial de Angular
Componentes independientes de Angular 20: entradas de señal, detección de cambios OnPush, plantillas en línea, cero NgModules, cero CSS.
Qué vas a construir
Un pequeño formulario de contacto: campo con etiqueta, botón de envío, enlace bidireccional, comportamiento real de teclado y lector de pantalla; primero desnudo, luego con estilo, en unos 10 minutos.
Antes de empezar
Necesitas git, Node 22 o posterior y pnpm 10 o posterior. Sirve cualquier proyecto de Angular 20: Angular CLI o Analog.
Paso 1: Obtén el código
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-angular-headless
pnpm install
pnpm test # vitest + TestBed, all 571 components
pnpm run build-storybook
Los componentes son archivos planos en components/ —
Button.ts, TextInput.ts ; cada uno es un componente independiente con plantilla en línea, además de su spec y su story. El barrel
index.ts reexporta cada componente; pnpm build
produce un paquete en Angular Package Format mediante ng-packagr.
Paso 2: Tu primer componente
import { Component } from "@angular/core";
import { Button } from "@lilydesignsystem/angular-headless";
@Component({
selector: "app-save-bar",
standalone: true,
imports: [Button],
template: `<lily-button (click)="save()">Save</lily-button>`,
})
export class SaveBar {
save() { console.log("saved"); }
}
Cada componente declara un selector lily-* y renderiza el elemento semántico con su gancho de clase:
<button class="button" type="button"> aquí. Las entradas se basan en señales: label() para un aria-label
para anular, className() para añadir clases.
Paso 3: Compón un formulario pequeño
import { Component, signal } from "@angular/core";
import { Form, Field, TextInput, Button } from "@lilydesignsystem/angular-headless";
@Component({
selector: "app-contact-form",
standalone: true,
imports: [Form, Field, TextInput, Button],
template: `
<lily-form label="Contact form" (submit)="handleSubmit($event)">
<lily-field label="Full name" [required]="true">
<lily-text-input id="name" label="Full name" [(value)]="name" />
</lily-field>
<lily-button type="submit">Save</lily-button>
</lily-form>
`,
})
export class ContactForm {
name = signal("");
}
lily-field toma la etiqueta visible como su propia entrada
label en lugar de un componente de etiqueta anidado: pásala una vez, en el campo, y el lily-text-inputcorrespondiente de
label aporta el nombre accesible. Los componentes similares a entradas exponen valores model() así que el enlace bidireccional banana-box [(value)]
funciona de inmediato, compatible con zoneless en todo momento.
Verificado el 2026-08-29: un borrador anterior anidaba un componente Label aparte dentro de lily-field, lo que renderizaba dos etiquetas (una vacía) porque
lily-field ya renderiza la suya propia a partir de la entrada
label ; se detectó revisando el código fuente del componente.
La ruta compuesta del formulario de contacto
es el mismo patrón, ejercitado con pruebas e2e y axe reales.
Comprueba tu trabajo: ejecuta la aplicación y el formulario se ve sencillo, tal como se pretendía: el estilo aún está por venir. Pulsa Tab: el foco recorre cada control, se anuncia la etiqueta y el envío funciona. El comportamiento está hecho; el aspecto es tuyo y es el siguiente paso.
Paso 4: Dale estilo
Da estilo a los ganchos de clase: .button, .text-input,
.label, .field , en styles.css, o añade un tema ya hecho al arreglo styles en
angular.json / enlázalo en index.html:
<link rel="stylesheet" href="/themes/adobe-spectrum.css" />
Paso 5: Míralo todo funcionando
La aplicación de ejemplo Angular + Analog muestra el catálogo completo con enrutamiento basado en archivos y Vite:
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-angular-examples
pnpm install && pnpm run dev
Próximos pasos
- Temas : cambio en tiempo de ejecución con el asistente theme-picker de Angular.
- Asistentes de preferencias : idioma y tamaño del texto.
- Catálogo de componentes : propiedades, ARIA y teclado por componente.
¿Dudas por el camino? La página de ayuda está llena de respuestas, y si algún paso puede ser más claro, cuéntanoslo y con gusto mejoraremos el tutorial.