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

@ravindrasingh040690/ui

v0.1.0

Published

Accessible, standalone Angular UI components with a CSS custom-property theme system.

Readme

@ravindrasingh040690/ui

Accessible, standalone Angular components with a CSS custom-property theme system.

  • Angular 22, standalone components, signal inputs and OnPush everywhere
  • No runtime dependencies beyond @angular/core, @angular/common and @angular/forms
  • One token file drives every colour, radius, shadow and spacing value
  • Light and dark themes with no per-component overrides
  • Tree-shakeable, with the theme stylesheet explicitly preserved

Installation

npm install @ravindrasingh040690/ui

Requires Angular ^22.0.0.

Setup

Add the theme stylesheet to your build so the --ui-* custom properties exist:

// angular.json
{
  "styles": ["node_modules/@ravindrasingh040690/ui/styles/ui-theme.css", "src/styles.scss"],
}

Because the components fall back to build-time defaults, they render acceptably even before this step — but colours and radii come from the tokens, so do not skip it.

Then import only what you use. Components are standalone, so there is no module to install:

import { Component } from '@angular/core';
import { UiButton, UiCard, UiBadge } from '@ravindrasingh040690/ui';

@Component({
  selector: 'app-pricing',
  imports: [UiButton, UiCard, UiBadge],
  template: `
    <ui-card title="Team" subtitle="For growing teams">
      <ui-badge uiCardActions variant="success" [dot]="true">Live</ui-badge>
      <p>Unlimited projects and SSO.</p>
      <div uiCardFooter>
        <ui-button variant="primary">Upgrade</ui-button>
      </div>
    </ui-card>
  `,
})
export class PricingComponent {}

Components

<ui-button>

Wraps a native <button>, so keyboard, focus and form semantics stay intact. Native (click) works as usual and does not fire while the button is disabled or loading.

| Input | Type | Default | Notes | | ----------- | -------------------------------------------------------------- | ----------- | ------------------------------------------------------ | | variant | 'primary' \| 'secondary' \| 'outline' \| 'ghost' \| 'danger' | 'primary' | | | size | 'sm' \| 'md' \| 'lg' | 'md' | | | type | 'button' \| 'submit' \| 'reset' | 'button' | | | disabled | boolean | false | | | loading | boolean | false | Shows a spinner, disables the button, sets aria-busy | | block | boolean | false | Full width | | ariaLabel | string \| null | null | Use for icon-only buttons |

<ui-button variant="primary" (click)="save()">Save</ui-button>
<ui-button variant="danger" [loading]="deleting()">Delete</ui-button>
<ui-button variant="outline" size="sm" ariaLabel="Close panel">✕</ui-button>

<ui-input>

Implements ControlValueAccessor, so [formControl], formControlName, [(ngModel)] and ngModel all work without a wrapper.

| Input | Type | Default | Notes | | -------------- | --------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------- | | label | string | '' | Rendered as a real <label for> | | type | 'text' \| 'email' \| 'number' \| 'password' \| 'search' \| 'tel' \| 'url' | 'text' | | | placeholder | string | '' | | | hint | string | '' | Shown below the field while there is no error | | error | string | '' | Presence marks the field invalid and is announced via role="alert" | | size | 'sm' \| 'md' \| 'lg' | 'md' | | | autocomplete | string \| null | null | | | inputmode | string \| null | null | | | id | string | auto | Override the generated control id | | required | boolean | false | Sets the native attribute and renders an asterisk | | disabled | boolean | false | Merged with the forms API's disabled state | | readonly | boolean | false | Declared as readOnly, exposed under the name readonly | | clearable | boolean | false | Adds a button that resets the value to '' | | clearLabel | string | 'Clear' | Accessible name for the clear button |

Slots: [uiPrefix] and [uiSuffix] for affixes. They are projected verbatim, so style them from your own component stylesheet.

<ui-input label="Email" type="email" [formControl]="email" [error]="emailError()" />

<ui-input label="Amount" size="sm" hint="Charged monthly">
  <span uiPrefix>$</span>
  <span uiSuffix>USD</span>
</ui-input>

Values are always strings, including for type="number" — this matches how ControlValueAccessor behaves for any other Angular input control.

<ui-card>

| Input | Type | Default | Notes | | ------------- | -------------------------------------- | ------------ | ---------------------------------------------- | | variant | 'elevated' \| 'outlined' \| 'filled' | 'elevated' | | | padding | 'none' \| 'sm' \| 'md' \| 'lg' | 'md' | Applied to header, body and footer alike | | interactive | boolean | false | Hover elevation plus a tabindex="0" tab stop | | title | string | '' | Its presence enables the header row | | subtitle | string | '' | |

Slots: the default slot is the body, [uiCardActions] lands in the header, [uiCardFooter] becomes a separated footer. Both attribute slots are hidden automatically when nothing is projected.

The header only renders when title or subtitle is set, so [uiCardActions] needs one of them.

<ui-card title="Starter" subtitle="For side projects">
  <ui-button uiCardActions variant="ghost" size="sm">Edit</ui-button>
  <p>One project and one collaborator.</p>
  <div uiCardFooter>
    <ui-button size="sm">Choose Starter</ui-button>
  </div>
</ui-card>

<ui-modal>

Built on the native <dialog> element, so focus trapping, inert background content, top-layer stacking and Esc handling all come from the platform.

| Input | Type | Default | Notes | | ----------------- | ------------------------------ | --------- | -------------------------------- | | open | two-way model<boolean> | false | [(open)]="isOpen" | | title | string | '' | Also used as the accessible name | | size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | | | closeOnBackdrop | boolean | true | | | closeOnEscape | boolean | true | | | showClose | boolean | true | | | ariaLabel | string \| null | null | Used when there is no title | | closeLabel | string | 'Close' | |

| Output | Payload | When | | ----------- | ----------------------------------- | ---------------------------------------------------- | | dismissed | 'close' \| 'backdrop' \| 'escape' | Only when the user closed the dialog | | closed | – | Whenever the dialog finishes closing, for any reason |

Method: close() closes the dialog programmatically without reporting a dismissal.

Slots: the default slot is the scrollable body, [uiModalFooter] becomes the action row.

<ui-modal [(open)]="confirmOpen" title="Delete project" size="sm" (dismissed)="log($event)">
  <p>This cannot be undone.</p>
  <div uiModalFooter>
    <ui-button variant="ghost" (click)="confirmOpen.set(false)">Cancel</ui-button>
    <ui-button variant="danger" (click)="remove()">Delete</ui-button>
  </div>
</ui-modal>

<ui-alert>

| Input | Type | Default | Notes | | ------------- | ---------------------------------------------- | ----------- | ----- | | variant | 'info' \| 'success' \| 'warning' \| 'danger' | 'info' | | | appearance | 'soft' \| 'solid' \| 'outline' | 'soft' | | | title | string | '' | | | dismissible | boolean | false | | | icon | boolean | true | | | closeLabel | string | 'Dismiss' | |

Output: dismissed — emitted once, when the user dismisses it.

danger and warning render with role="alert"; info and success use the polite role="status".

<ui-alert variant="success" title="Deployed">Build #482 is live.</ui-alert>
<ui-alert variant="danger" title="Payment failed" [dismissible]="true" (dismissed)="track()">
  Update your billing details.
</ui-alert>

<ui-badge>

| Input | Type | Default | Notes | | ------------ | ------------------------------------------------------------------------ | ----------- | ------------------------------ | | variant | 'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger' | 'neutral' | | | size | 'sm' \| 'md' \| 'lg' | 'md' | | | appearance | 'soft' \| 'solid' \| 'outline' | 'soft' | | | pill | boolean | false | Fully rounded | | dot | boolean | false | Prepends a coloured status dot |

<ui-badge variant="success" [dot]="true">Live</ui-badge>
<ui-badge variant="warning" appearance="outline">Degraded</ui-badge>
<ui-badge variant="primary" size="lg" [pill]="true">v0.1.0</ui-badge>

<ui-spinner>

| Input | Type | Default | Notes | | ---------- | -------------------------------------- | ----------- | ------------------------------------------------ | | size | 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | | | tone | 'primary' \| 'neutral' \| 'inherit' | 'primary' | inherit picks up the surrounding color | | label | string | 'Loading' | Announced to assistive technology, never painted | | overlay | boolean | false | Covers the nearest positioned ancestor | | backdrop | boolean | true | Dims the backdrop in overlay mode |

<ui-spinner />
<ui-spinner size="lg" tone="neutral" label="Fetching members" />

<div class="panel">
  <p>Content that is loading…</p>
  @if (loading()) {
  <ui-spinner [overlay]="true" />
  }
</div>

Theming

Every token is emitted as a --ui-* custom property on :root. Override them anywhere — no build step, no Sass, no ::ng-deep:

:root {
  --ui-color-primary: #0ea5e9;
  --ui-radius-md: 0px;
  --ui-font-family: 'Inter', system-ui, sans-serif;
}

Because the same values are compiled into the components as fallbacks, a token you have not overridden still resolves correctly.

Dark mode

<!-- Always dark -->
<html data-ui-theme="dark">
  <!-- Follow the operating system -->
  <html data-ui-theme="auto"></html>
</html>

[data-ui-theme="dark"] can be applied to any element, not just <html>, which makes scoped themes possible.

Overriding at compile time

If you would rather not ship a second stylesheet, consume the Sass source directly and let the components inherit your values:

// angular.json — so that `@use 'ui-theme'` resolves
"stylePreprocessorOptions": {
  "includePaths": ["node_modules/@ravindrasingh040690/ui/styles"]
}
// styles.scss
@use '@ravindrasingh040690/ui/styles/tokens' as ui with (
  $defaults: map.merge(
      ui.$defaults,
      (
        'color-primary': #0ea5e9,
      )
    )
);

@use 'ui-theme';

Accessibility notes

  • The modal uses <dialog> + showModal(), so focus is trapped and background content is inert by the browser, not by JavaScript.
  • Focus rings use :focus-visible only, so pointer users never see them.
  • prefers-reduced-motion: reduce disables the animations and transitions the library ships.
  • Alerts select role="alert" only for danger and warning; the rest announce politely.

License

MIT