ngx-x-ui
v0.0.1
Published
Librería de componentes de UI para Angular 22, construida sobre Tailwind CSS 4 y tematizable por completo mediante design tokens (--x-*).
Maintainers
Readme
ngx-x-ui
Biblioteca de componentes de UI para Angular 22, construida sobre Tailwind CSS 4 y tematizable por completo mediante design tokens.
- Sin dependencias de iconos. Los iconos son contenido proyectado: usa el paquete que quieras.
- Sin preflight. El tema no resetea tus
h1,ul,buttonnitable. - Sin Tailwind obligatorio. El tema se publica ya compilado como CSS plano.
- Tematizable de verdad. 87 tokens
--x-*cubren color, tipografía, densidad, radio, elevación, movimiento y foco. Ningún componente lleva un valor fijo. - Componentes standalone, basados en signals, sin
NgModule.
⚠️ Licencia propietaria
Esto no es software libre ni de código abierto. Puedes instalarlo y usarlo en tus aplicaciones, incluidas las comerciales, y distribuirlo embebido en ellas. No puedes modificarlo, parchearlo, bifurcarlo ni republicarlo. Lee LICENSE antes de adoptarlo — en particular la §3, que detalla toda la personalización que sí está permitida (tokens, temas propios,
className, envolver la API).
Requisitos
| Peer dependency | Versión |
| ----------------- | --------- |
| @angular/core | ^22.1.0 |
| @angular/common | ^22.1.0 |
La única dependencia en tiempo de ejecución es
tailwind-merge, que se instala sola y nunca la llamas
tú: es un detalle de implementación del input className.
Instalación
npm install ngx-x-uiImporta un tema una sola vez, en el CSS global de tu aplicación:
@import 'ngx-x-ui/theme.css';Y ya puedes usar los componentes. Son standalone: van directos al array imports.
import { Component } from '@angular/core';
import { XButton } from 'ngx-x-ui';
@Component({
selector: 'app-root',
imports: [XButton],
template: `<x-button severity="success" (clicked)="guardar()">Guardar</x-button>`,
})
export class App {
protected guardar() {}
}Componentes
<x-button>
<x-button severity="danger" size="large" outlined (clicked)="borrar()">Borrar</x-button>| Input | Tipo | Por defecto | Descripción |
| -------------- | --------------------------------- | ----------------------------------- | --------------------------------------------------- |
| severity | XSeverity | 'primary' | Eje de color. |
| size | XSize | de provideXUi(), si no 'medium' | Eje de densidad. |
| type | 'button' \| 'submit' \| 'reset' | 'button' | Tipo del <button> nativo. |
| disabled | boolean | false | |
| loading | boolean | false | Muestra un spinner y oculta los iconos proyectados. |
| text | boolean | false | Apariencia sin fondo ni borde. |
| outlined | boolean | false | Apariencia con borde y sin relleno. |
| link | boolean | false | Apariencia de enlace. |
| raised | boolean | false | Sombra. Solo aplica a la apariencia sólida. |
| plain | boolean | false | Neutraliza el color. Solo en text y outlined. |
| rounded | boolean | false | Radio completo. |
| iconOnly | boolean | false | Padding cuadrado para un botón sin texto. |
| ariaLabel | string | — | Obligatorio si el botón no tiene texto visible. |
| ariaHasPopup | XAriaHasPopup | — | |
| ariaExpanded | boolean | — | |
| ariaControls | string | — | |
| className | string | — | Clases extra para el <button> interno. |
| styleObject | XStyleObject | — | Estilos en línea para el <button> interno. |
| Output | Tipo |
| --------- | ------------ |
| clicked | MouseEvent |
Las apariencias son excluyentes y se resuelven con una precedencia fija:
link > text > outlined > sólido. No se apilan.
Los iconos son contenido, no un input
La biblioteca no depende de ningún paquete de iconos y no acepta un input icon. Proyecta el tuyo
y la posición la decide el orden en el DOM:
<x-button><svg>…</svg> Guardar</x-button>
<x-button>Siguiente <svg>…</svg></x-button>
<x-button iconOnly ariaLabel="Cerrar"><svg>…</svg></x-button>El botón dimensiona lo que le des mediante --x-btn-icon-size, así que un icono proyectado no
necesita prop de tamaño: sigue al tema. Con loading, los iconos se ocultan y el spinner ocupa su
sitio en vez de amontonarse al lado.
<x-button-group>
Une varios botones: cuadra los radios interiores, conserva los exteriores y colapsa el borde
compartido. No necesita saber cuántos hijos tiene, así que funciona con @for.
<x-button-group ariaLabel="Alineación">
<x-button outlined>Izquierda</x-button>
<x-button outlined>Centro</x-button>
<x-button outlined>Derecha</x-button>
</x-button-group>| Input | Tipo | Descripción |
| ------------- | -------------- | ----------------------------------------- |
| ariaLabel | string | Nombra el grupo para tecnología asistiva. |
| className | string | |
| styleObject | XStyleObject | |
<x-split-button> y <x-menu-item>
Botón con menú desplegable, siguiendo el patrón WAI-ARIA de menu button. Acepta todos los inputs
de <x-button> salvo iconOnly y los pasos de ARIA, que gestiona él mismo para construir la
relación entre el disparador y el panel.
<x-split-button severity="secondary" (clicked)="guardar()" (opened)="onOpen()">
Guardar
<x-menu-item (selected)="guardarComo()">Guardar como…</x-menu-item>
<x-menu-item (selected)="guardarTodo()">Guardar todo</x-menu-item>
<x-menu-item separator />
<x-menu-item disabled>Exportar</x-menu-item>
</x-split-button>Inputs propios de <x-split-button>:
| Input | Tipo | Por defecto | Descripción |
| ------------------ | -------- | ---------------- | --------------------------------- |
| menuAriaLabel | string | 'More actions' | Nombra el disparador y el panel. |
| triggerClassName | string | — | Clases para la mitad del chevron. |
| menuClassName | string | — | Clases para el panel del menú. |
| Output | Tipo | Se emite cuando |
| --------- | ------------ | ---------------------------- |
| clicked | MouseEvent | Se pulsa la mitad principal. |
| opened | void | Se abre el menú. |
| closed | void | Se cierra el menú. |
<x-menu-item>:
| Input | Tipo | Descripción |
| ------------- | -------------- | ------------------------------------- |
| disabled | boolean | Se salta con las flechas del teclado. |
| separator | boolean | Regla divisoria; no es navegable. |
| className | string | |
| styleObject | XStyleObject | |
| Output | Tipo |
| ---------- | ------ |
| selected | void |
El teclado lo gestiona el <x-split-button>, no el ítem: flechas arriba/abajo con roving
tabindex que salta deshabilitados y separadores, Home/End, Escape para cerrar y devolver el
foco al disparador.
<x-card>
Tres zonas de proyección. Las ranuras son selectores de atributo, así que no hay que importar
ninguna directiva y el encabezado lo pones tú con el nivel semántico que corresponda — la tarjeta
nunca impone un <h3>.
<x-card outlined>
<h2 xCardHeader>Título</h2>
<p>Cuerpo de la tarjeta. Todo lo que no lleve atributo cae aquí.</p>
<div xCardFooter>
<x-button text>Cancelar</x-button>
<x-button>Aceptar</x-button>
</div>
</x-card>Una zona sin contenido se colapsa sola por CSS (.x-card > :empty), no con @if, así que
contenido que llega tarde dentro de un @for no hace parpadear la estructura.
Como en el botón, las apariencias son excluyentes y siguen una precedencia fija:
outlined > filled > elevada (la de por defecto, con sombra).
| Input | Tipo | Por defecto | Descripción |
| ------------- | -------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| size | XSize | de provideXUi(), si no 'medium' | Densidad del padding. |
| outlined | boolean | false | Borde en lugar de sombra. |
| filled | boolean | false | Superficie rellena, sin sombra ni borde. |
| noPadding | boolean | false | Quita el padding del cuerpo, para contenido a sangre (una tabla, una imagen). El encabezado y el pie conservan el suyo: si no, sus divisorias quedarían pegadas al texto. |
| ariaLabel | string | — | Nombra la tarjeta y es lo que la promueve a role="group". La mayoría son decorativas y no quieren ninguna de las dos cosas. |
| className | string | — | |
| styleObject | XStyleObject | — | |
<x-card> no expone headerClassName ni similares a propósito: sus zonas se alcanzan por los
locales desde el host.
<!-- Encabezado sin línea divisoria -->
<x-card [styleObject]="{ '--x-card-divider-width': '0' }">…</x-card>Defaults de aplicación
import { bootstrapApplication } from '@angular/platform-browser';
import { provideXUi } from 'ngx-x-ui';
bootstrapApplication(App, {
providers: [provideXUi({ size: 'small' })],
});Precedencia, de lo más concreto a lo más general: el input del elemento → provideXUi() → el
valor por defecto de la biblioteca (medium).
size es la única clave hoy. severity no está, y es deliberado: la severidad es semántica de
cada uso (danger en un borrar, success en un confirmar), no un estilo de casa.
Temas
Un tema es una hoja de estilos completa: se importa una en lugar de la otra, no se combinan.
@import 'ngx-x-ui/theme.css'; /* por defecto (alias de themes/x-ui.css) */
@import 'ngx-x-ui/themes/material.css'; /* Material Design 3 */Los tokens en crudo se publican también sin compilar, por si quieres construir tu propio tema partiendo de las custom properties sin arrastrar Tailwind:
@import 'ngx-x-ui/themes/x-ui/tokens.css';
@import 'ngx-x-ui/themes/material/tokens.css';Personalizar con tokens
Los 87 tokens --x-* se declaran en una capa (@layer x-ui-tokens), así que una redefinición
tuya sin capa siempre gana, sin importar el orden de las hojas de estilo:
:root {
--x-color-primary: #7c3aed;
--x-color-primary-hover: #6d28d9;
--x-color-primary-active: #5b21b6;
--x-color-text-on-primary: #ffffff;
--x-radius-md: 2px;
--x-font-family: 'Inter', sans-serif;
--x-control-padding-x-md: 1.25rem;
}Familias disponibles: --x-color-* (una cuaterna base/hover/active/subtle más un color de texto
para cada una de las 8 severidades, además de superficies, bordes y texto), --x-font-*,
--x-line-height-*, --x-letter-spacing, --x-control-padding-*, --x-control-gap-*,
--x-control-icon-*, --x-radius-*, --x-border-width, --x-shadow-1/2/3,
--x-surface-padding-*, --x-focus-width, --x-focus-offset, --x-transition-duration,
--x-motion-spin-duration, --x-opacity-disabled, --x-z-raised y --x-z-dropdown.
Severidades: primary, secondary, success, info, warn, help, danger, contrast.
className y styleObject
En Angular, class y style los reserva el elemento anfitrión. Un <x-button class="w-full">
estiliza el <x-button>, no el <button> de dentro. Por eso existen estos dos inputs, que sí
llegan al elemento real:
<x-button className="w-full" [styleObject]="{ marginBlockStart: '1rem' }">Ancho completo</x-button>className pasa por tailwind-merge, así que una clase
en conflicto desplaza la del componente en vez de competir con ella por especificidad. Dos
detalles que conviene saber:
- Las variantes se resuelven por separado.
className="bg-red-500"no desplaza elhover:bg-…del componente; un cambio de color necesita también su propiohover:. - Necesitas Tailwind en tu propio build para que exista una utilidad como
px-8. El tema publicado solo contiene las clases que usan los componentes. Si no usas Tailwind,classNameacepta igualmente tus clases CSS de siempre.
Accesibilidad
- Indicadores de foco con
outline, no solo cambio de fondo, para que sobrevivan aforced-colors. - Bloque
prefers-reduced-motionsin capa en:root, de modo que la preferencia del usuario gana siempre al tema. El spinner se ralentiza, nunca se detiene. - El panel del menú se oculta con el atributo
hidden, lo que lo saca del árbol de accesibilidad y del orden de tabulación. - Propiedades lógicas (
start/end) en todo lo posicional, para que funcione en RTL.
Licencia
Propietaria. Copyright © 2026 Henry Rincon. Todos los derechos reservados. Consulta LICENSE para los términos completos.
Para permisos que excedan lo que concede la licencia: [email protected]
