npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-*).

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, button ni table.
  • 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 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-ui

Importa 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 el hover:bg-… del componente; un cambio de color necesita también su propio hover:.
  • 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, className acepta igualmente tus clases CSS de siempre.

Accesibilidad

  • Indicadores de foco con outline, no solo cambio de fondo, para que sobrevivan a forced-colors.
  • Bloque prefers-reduced-motion sin 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]