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

@besaitech/ng-design-system

v0.0.64

Published

Reusable **Angular 20** component library (`sds-` prefix) styled with **Tailwind CSS v4**, based on the Saitech visual language. Buttons, inputs, selects, tables, tabs, accordions, a date picker, phone input, plus the app shell (top bar + sidebar).

Readme

@besaitech/ng-design-system

Reusable Angular 20 component library (sds- prefix) styled with Tailwind CSS v4, based on the Saitech visual language. Buttons, inputs, selects, tables, tabs, accordions, a date picker, phone input, plus the app shell (top bar + sidebar).

Important: components are styled with Tailwind utility classes that live in their templates — the package does not ship compiled CSS. Your app must run Tailwind v4, scan this package, and define the design tokens (see Tailwind setup). Without that step the components render unstyled.


Requirements

  • Angular 20.3+, 21.x or 22.x (@angular/common, core, forms, platform-browser, router)
  • Tailwind CSS v4 in the consuming app

1. Install

npm install @besaitech/ng-design-system

Public npm registry — no .npmrc, no scope mapping and no read token required.

2. Tailwind setup

The Angular peer dependencies (@angular/common, core, forms, …) are already present in any Angular 20.3+ / 21 / 22 app. If you don't have Tailwind yet, add it as a dev dependency:

npm install -D tailwindcss @tailwindcss/postcss

3. Tailwind v4 setup

This is the step that makes the components look right. Tailwind v4 is CSS-first — there is no tailwind.config.js.

a. Register the PostCSS plugin. Create .postcssrc.json at your project root:

{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}

b. Import Tailwind, scan the library, and define the tokens in your global stylesheet (src/styles.css). The @source line is what tells Tailwind to generate the utility classes used inside the library, and the @theme block defines the colors/radii those classes refer to — both are required:

@import "tailwindcss";

/* Generate the utilities used inside @besaitech/ng-design-system templates.
   Path is relative to THIS file (src/styles.css → ../node_modules). */
@source "../node_modules/@besaitech/ng-design-system";

/* Tokens. BOTH imports are required, and both must come AFTER the tailwindcss import —
   Tailwind v4 processes @theme in imported files and later declarations win.
   base.css = surfaces, ink, accents, status, radius, layout vars (15 tokens).
   The brand preset = the 11 --color-brand-* steps, and nothing else. */
@import "@besaitech/ng-design-system/themes/base.css";
@import "@besaitech/ng-design-system/themes/saitech.css";  /* blues */
/* @import "@besaitech/ng-design-system/themes/belvigo.css";   black */
/* @import "@besaitech/ng-design-system/themes/belvigo-emerald.css";   emerald (Belvigo merchant);
   keeps green primary fills in dark mode through the --sds-dark-* hooks listed in base.css */

/* Override any base default in your own @theme afterwards, e.g. the typeface. */
@theme {
  /* Neutrals / surfaces (already provided by base.css — shown for reference) */
  --color-canvas: #f5f6f8;
  --color-surface: #ffffff;
  --color-muted: #f8f9fb;
  --color-border: #e6e9f0;
  --color-ink: #1b2440;
  --color-ink-soft: #5b6577;
  --color-ink-faint: #8a93a6;

  /* Accent icon-tiles (foreground + soft background) */
  --color-accent-blue: #2f6bd4;
  --color-accent-blue-soft: #e8effb;
  --color-accent-green: #1f9d57;
  --color-accent-green-soft: #e6f5ec;
  --color-accent-amber: #e0902a;
  --color-accent-amber-soft: #fdf0dd;
  --color-accent-red: #df4f6b;
  --color-accent-red-soft: #fde7ec;
  --color-accent-indigo: #6c5ce7;
  --color-accent-indigo-soft: #ece9fb;

  /* Status */
  --color-success: #27c06b;
  --color-warning: #f3b73c;
  --color-danger: #df4f6b;

  /* Radius */
  --radius-control: 0.5rem;
  --radius-card: 0.75rem;

  /* Typography */
  --font-sans: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
}

/* Layout dimensions used by <sds-shell> / <sds-sidebar> / <sds-topbar>.
   Only needed if you use the layout components. */
@layer base {
  :root {
    --sds-topbar-h: 64px;
    --sds-sidebar-w: 256px;
    --sds-sidebar-w-collapsed: 76px;
  }

  body {
    margin: 0;
    font-family: var(--font-sans);
    background-color: var(--color-canvas);
    color: var(--color-ink);
  }
}

Why @source? Tailwind v4 ignores node_modules by default. The explicit @source re-includes this package so the classes baked into its compiled templates produce real CSS. If components show up unstyled, this line (or a wrong relative path) is almost always the cause.

4. Use a component

All components are standalone — import the ones you need directly into a component's imports:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SdsButton, SdsInput, SdsIcon } from '@besaitech/ng-design-system';

@Component({
  selector: 'app-member-form',
  imports: [FormsModule, SdsButton, SdsInput, SdsIcon],
  template: `
    <sds-input label="Nom" [(ngModel)]="name" leadingIcon="search" clearable />

    <sds-button variant="primary" (click)="save()">
      <sds-icon sdsIconLeading name="user-plus" [size]="18" />
      Créer un membre
    </sds-button>
  `,
})
export class MemberForm {
  name = '';
  save() {/* ... */}
}

Form controls (sds-input, sds-textarea, sds-select, sds-multi-select, sds-toggle-switch, sds-date-picker, sds-phone-input) implement ControlValueAccessor, so they work with both template-driven ([(ngModel)]) and reactive (formControlName) forms.


Component catalog

Import names are the Sl… classes; the markup selectors are sds-….

Controls & primitives

| Component | Selector | Notes | | --- | --- | --- | | SdsButton | <sds-button> | variant (primary·secondary·tertiary·ghost·danger), size, loading, block, iconOnly; [sdsIconLeading]/[sdsIconTrailing] slots | | SdsInput | <sds-input> | CVA. label, hint, error, size, appearance (default·on-brand), leading/trailingIcon, clearable, required, invalid, inputId | | SdsTextarea | <sds-textarea> | CVA. Auto-grow, label, hint, error, required | | SdsTimeInput | <sds-time-input> | CVA time of day, 24 h, value "HH:mm" | null; minuteStep, clearable, withSeconds | | SdsChipGroup / SdsChip | <sds-chip-group> / <sds-chip> | Filter/choice chips: one or multiple (array), counts, [(value)] or CVA; single toggle chip [(selected)] | | SdsToggleSwitch | <sds-toggle-switch> | CVA boolean toggle | | SdsSelect | <sds-select> | CVA single-select dropdown | | SdsMultiSelect | <sds-multi-select> | CVA multi-select with chips; (opened) / (closed) (emits the selection) | | SdsDatePicker / SdsCalendar | <sds-date-picker> / <sds-calendar> | CVA date field + popover calendar (date-utils helpers) | | SdsPhoneInput | <sds-phone-input> | CVA phone field with country selector (phone-countries) | | SdsIcon | <sds-icon> | Inline-SVG icon; name, size, strokeWidth, filled | | SdsAvatar | <sds-avatar> | Image, initials or fallbackIcon + status dot; shape (circle·rounded·square), decorative; broken image falls back | | SdsBadge | <sds-badge> | Status pill; variant, dot | | SdsCard | <sds-card> | Surface container; variant, interactive, padding |

Composite

| Component | Selectors | | --- | --- | | Tabs | <sds-tabs> · <sds-tab-list> · <sds-tab> · <sds-tab-panels> · <sds-tab-panel> — controlled + (tabSelect) for query-param/guarded tabs; panels optional; <sds-tab-list [ariaLabel]> | | Accordion | <sds-accordion> · <sds-accordion-panel> · <sds-accordion-header> · <sds-accordion-content> | | Table | <sds-table> · ng-template[sdsCell] · [sdsHeader] · [sdsRowExpansion] · [sdsTableEmpty] — rowClickable, [rowClass] | | Paginator | <sds-paginator> |

Layout (use the design tokens / layout vars from step 3b)

| Component | Selector | Notes | | --- | --- | --- | | SdsShell | <sds-shell> | Top bar + sidebar + scrollable content; [sdsTopbar]/[sdsSidebar] slots | | SdsTopbar | <sds-topbar> | appName, user, (logout); [sdsLogo] slot | | SdsSidebar + SdsNavGroup + SdsNavItem | <sds-sidebar> … | Collapsible nav rail; collapse state via root-provided SidebarStore; items render routerLink or <button> |

Theme & cross-app preferences

SdsThemeService holds the person's mode — 'system' | 'light' | 'dark', persisted to localStorage under sds-theme — and resolves it to the painted theme (light/dark); system follows the OS live. toggle() / setTheme() set an explicit theme, setMode('system') goes back to following the OS.

Apps of one product can carry the person's language and theme in the URL when they link to each other, so the page that opens matches. Sending side:

import { appendUiPrefs } from '@besaitech/ng-design-system';

appendUiPrefs('https://auth.example/continue#token=abc', { lang: translate.getCurrentLang(), theme: theme.mode() });
// → https://auth.example/continue?lang=fr&theme=dark#token=abc

Never put them inside a nested redirect a server matches exactly (return_to, redirect_uri) — append them to the outer URL. Receiving side: an inline script in <head> of index.html applies, persists and strips them before first paint (no flash, and no import of this library at the root). LANG_KEY is the app's own language storage key; keep LANGS in step with the app's languages. readUiPrefs / stripUiPrefs are the same logic for code and tests.

<script>
  (function () {
    var LANG_KEY = 'app.lang', LANGS = ['en', 'fr', 'nl'], THEME_KEY = 'sds-theme', MODES = ['system', 'light', 'dark'];
    var url = new URL(location.href), q = url.searchParams, lang = q.get('lang'), mode = q.get('theme');
    try {
      if (LANGS.indexOf(lang) >= 0) localStorage.setItem(LANG_KEY, lang);
      if (MODES.indexOf(mode) >= 0) localStorage.setItem(THEME_KEY, mode);
    } catch (e) {}
    try { lang = localStorage.getItem(LANG_KEY) || lang; mode = localStorage.getItem(THEME_KEY) || mode; } catch (e) {}
    if (LANGS.indexOf(lang) >= 0) document.documentElement.lang = lang;
    var dark = mode === 'dark' || (mode !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches);
    document.documentElement.classList.toggle('dark', dark);
    document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
    if (q.has('lang') || q.has('theme')) {
      q.delete('lang'); q.delete('theme');
      history.replaceState(history.state, '', url.pathname + url.search + url.hash);
    }
  })();
</script>

Labels & languages

The library ships no translations of its own. Every visible or spoken string — placeholders, empty states, validation messages and the accessible names of icon-only buttons — is an input with an English default (since 0.0.54; earlier releases defaulted some components to French). An app serving more than one language binds its translations, e.g. <sds-select [placeholder]="'common.select' | translate" [clearLabel]="'common.clear' | translate" />. Locale-aware parts take a locale input: sds-date-picker/sds-calendar (month and weekday names, default en-GB), sds-input-number and sds-table (number/currency formatting), sds-phone-input (country names via Intl.DisplayNames; empty = built-in English names).


Troubleshooting

| Symptom | Cause / fix | | --- | --- | | 403 insufficient_scope | Token is missing read_package_registry scope | | Components render unstyled | Missing/incorrect @source line, or no theme preset imported (step 3b) | | Brand colours are wrong / absent | The theme preset is imported BEFORE @import "tailwindcss", so it is overridden | | sds-icon shows nothing | The name isn't in the icon registry; check available names via the SdsIconName type |

Versioning

Semver. Each release is published from the workspace with npm run release:lib (bump → build → publish). Pin a version in your app and bump deliberately.