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

@wizards-space/ui

v0.13.3

Published

Angular 20 component library on top of ng-zorro-antd 20, themed entirely through CSS custom properties. Ships **no** vendor SDKs — no Firebase, no API clients, no i18n runtime — so it drops into any app.

Readme

@wizards-space/ui

Angular 20 component library on top of ng-zorro-antd 20, themed entirely through CSS custom properties. Ships no vendor SDKs — no Firebase, no API clients, no i18n runtime — so it drops into any app.

This document is written to be followed step by step, including by a coding agent setting up a fresh application. Follow the sections in order.


1. Install

npm install @wizards-space/ui

Peer dependencies your app must already have:

| Package | Range | | --- | --- | | @angular/animations, @angular/common, @angular/core, @angular/forms, @angular/platform-browser, @angular/router | ^20.1.0 | | ng-zorro-antd | ^20.1.0 | | rxjs | ^7.8.0 |


2. Wire up styles

2.1 Add the global stylesheet

The library's stylesheet must be loaded once, globally. It is LESS and imports ng-zorro's variable theme, so it has to go through the build's styles array — not through a component's styleUrls.

In angular.json / project.json, in the build target options:

{
  "styles": [
    "src/styles.less",
    "node_modules/@wizards-space/ui/styles/styles.less"
  ],
  "stylePreprocessorOptions": {
    "includePaths": ["node_modules/@wizards-space/ui/styles"]
  }
}

includePaths is required — partials reference each other by bare name.

Repeat the same two entries in the serve target if it does not inherit from build, and in the Storybook target if the app has one.

2.2 Copy the assets

Fonts and icons are referenced by relative URL from the stylesheet, so they must be served from /assets:

{
  "assets": [
    {
      "glob": "**/*",
      "input": "node_modules/@wizards-space/ui/assets/fonts",
      "output": "/assets/fonts"
    },
    {
      "glob": "**/*",
      "input": "node_modules/@wizards-space/ui/assets/icons",
      "output": "/assets/icons"
    },
    {
      "glob": "**/*",
      "input": "node_modules/@ant-design/icons-angular/src/inline-svg/",
      "output": "/assets"
    }
  ]
}

The last entry is ng-zorro's icon set — omit it only if the app already provides it.

2.3 Activate a theme at bootstrap

Nothing is styled until the wiz-theme attribute is set on <body>. Do it once, at startup:

// app.config.ts
import { ApplicationConfig, inject, provideAppInitializer } from '@angular/core';
import { WizThemeService } from '@wizards-space/ui';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAppInitializer(() => inject(WizThemeService).init()),
    // …
  ],
};

At this point the app renders with the library's default look. Sections 3 and 4 make it yours.


3. Theming — how it works

Tokens are layered, each layer resolving into the one below:

primitives  →  colors (semantic)  →  components  →  ng-zorro LESS variables
  • components — ~1600 tokens, ~95% of them plain var(--wiz-colors-…) redirects. You almost never touch this layer.

  • colors — the semantic layer, split into three groups:

    | Group | Count | Role | | --- | --- | --- | | --wiz-colors-base-* | 160 | raw palette — shared | | --wiz-colors-neutral-* | 57 | greys, borders, backgrounds — usually shared | | --wiz-colors-brand-* | 61 | this is what an app overrides |

Each layer is scoped by an attribute selector, e.g. [wiz-theme~="colors-light"]. WizThemeService writes a space-separated list of layer names onto <body>:

<body wiz-theme="colors-light editor-dark dimensions-default wiz-styles typography components">

Because ~= matches any single word in that list, adding your own layer is just appending a name — your layer is loaded after the library's, so it wins.


4. Give the app its own brand

Step 1 — write a brand layer

Create src/styles/brand.css. Override only the --wiz-colors-brand-* tokens you care about; anything you omit falls through to the library default.

[wiz-theme~='brand-myapp'] {
  --wiz-colors-brand-color-primary: rgb(88, 60, 220);
  --wiz-colors-brand-color-primary-hover: rgb(112, 88, 232);
  --wiz-colors-brand-color-primary-active: rgb(66, 42, 180);
  --wiz-colors-brand-color-primary-bg: rgb(240, 236, 255);
  --wiz-colors-brand-color-primary-bg-hover: rgb(226, 220, 252);
  --wiz-colors-brand-color-primary-border: rgb(190, 176, 246);
  --wiz-colors-brand-color-primary-border-hover: rgb(150, 128, 240);
  --wiz-colors-brand-color-primary-text: rgb(88, 60, 220);
  --wiz-colors-brand-color-primary-text-hover: rgb(112, 88, 232);
  --wiz-colors-brand-color-primary-text-active: rgb(66, 42, 180);
}

The full list of overridable names is in node_modules/@wizards-space/ui/styles/figma/wiz-ds-colors-light.css — every --wiz-colors-brand-* declaration there is a valid override. Provide a matching brand-myapp-dark layer if the app supports dark mode.

Register it after the library stylesheet:

"styles": [
  "node_modules/@wizards-space/ui/styles/styles.less",
  "src/styles/brand.css"
]

Step 2 — tell WizThemeService to apply it

// app.config.ts
import {
  WizThemeService,
  WIZ_THEME_CONFIG,
  WIZ_DEFAULT_THEME_LAYERS,
} from '@wizards-space/ui';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: WIZ_THEME_CONFIG,
      useValue: {
        layers: {
          light: `${WIZ_DEFAULT_THEME_LAYERS.light} brand-myapp`,
          dark: `${WIZ_DEFAULT_THEME_LAYERS.dark} brand-myapp-dark`,
        },
        defaultTheme: 'light',
        // Namespace this per app — several apps on one origin share localStorage.
        storageKey: 'myapp_theme',
      },
    },
    provideAppInitializer(() => inject(WizThemeService).init()),
  ],
};

That is the whole theming setup. WizThemeSwitcherComponent toggles light/dark against this config; WizThemeService.themeChange emits the resolved theme.

Component-level styling

Inside your own components, always reference tokens rather than literal colours:

.panel {
  background: var(--wiz-colors-neutral-color-bg-container);
  border: 1px solid var(--wiz-colors-neutral-color-border);
  color: var(--wiz-colors-neutral-color-text);
}

A hard-coded hex will not follow the theme and will break dark mode.

Known global overrides

The partials were adopted 1:1 from Prometeus and still carry a few app-specific global rules. They are slated to move back into Prometeus; until then, be aware of them:

  • .ant-card is forced to width: 100% and gets a min-width below the sm breakpoint (_panel.less).
  • .gap-col, .row-green-background, .table-row-green-background, .table-row-red-background, .table-text-color-green, .table-text-color-red (_table.css); .gap-col uses hard-coded colours.
  • .wsc-panel references undefined tokens, so it renders without border and radius (_panel.less).

5. Optional providers

Two features need the app to supply an implementation. Skip a section if you do not use those components.

5.1 File storage

WizDisplayImgComponent, WizUploadFileDragAndDropComponent and WizDownloadFileService talk to storage through the WIZ_FILE_STORAGE token. The library deliberately ships no adapter.

import { WizFileStorage, WizFileMetadata } from '@wizards-space/ui';
import {
  deleteObject, getDownloadURL, getMetadata, getStorage, ref, uploadBytes,
} from 'firebase/storage';

export class FirebaseFileStorage implements WizFileStorage {
  private readonly storage = getStorage();

  getDownloadUrl(path: string): Promise<string> {
    return getDownloadURL(ref(this.storage, path));
  }

  async exists(path: string): Promise<boolean> {
    try {
      await getMetadata(ref(this.storage, path));
      return true;
    } catch (error) {
      if ((error as { code?: string }).code === 'storage/object-not-found') return false;
      throw error;
    }
  }

  async getMetadata(path: string): Promise<WizFileMetadata> {
    const meta = await getMetadata(ref(this.storage, path));
    return { name: meta.name, size: meta.size, contentType: meta.contentType };
  }

  async upload(path: string, file: File): Promise<void> {
    await uploadBytes(ref(this.storage, path), file);
  }

  async delete(path: string): Promise<void> {
    await deleteObject(ref(this.storage, path));
  }
}
{ provide: WIZ_FILE_STORAGE, useClass: FirebaseFileStorage }

WizInMemoryFileStorage is exported for tests, Storybook and prototyping.

5.2 ng-zorro modal and notification services

WizModalService and WizNotificationService wrap ng-zorro's, which must exist in the root injector:

import { importProvidersFrom } from '@angular/core';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { provideAnimations } from '@angular/platform-browser/animations';

providers: [provideAnimations(), importProvidersFrom(NzModalModule)]

6. Using components

Everything is standalone — import the component directly:

import { Component } from '@angular/core';
import { WizInputComponent, WizPageTitleComponent } from '@wizards-space/ui';

@Component({
  selector: 'app-example',
  imports: [WizPageTitleComponent, WizInputComponent],
  template: `
    <wiz-page-title titleText="Users" [showBack]="true" backLink="/users" />
    <wiz-input [formControl]="name" label="Name" />
  `,
})
export class ExampleComponent {}

[showBack]="true" adds a back arrow to wiz-page-title: with backLink (same forms as routerLink) it is a link, without it one step back in history. A (back) listener replaces either default.

Naming — two prefixes, on purpose

| Layer | Prefix | Examples | | --- | --- | --- | | TypeScript symbols | Wiz | WizInputComponent, WizThemeService, WizEmptyValuePipe | | Element selectors | wiz- | <wiz-input>, <wiz-page-title> | | CSS custom properties | --wiz- | --wiz-colors-brand-color-primary | | Theme attribute / layer names | wiz- | <body wiz-theme="colors-light …"> |

The design tokens use --wiz- because they are generated from Figma by the VVVariable Exporter plugin — renaming them by hand would be undone by the next export. They live in a namespace of their own, so they never collide with the TypeScript symbols.

There is no --wiz- prefix. Writing var(--wiz-…) fails silently: CSS does not warn about an undefined custom property, the declaration is simply dropped. Five such typos went unnoticed in this library for months. If a style has no effect, check the prefix first.

What's in the box

| Area | Components | | --- | --- | | Form controls | WizInput, WizInputNumber, WizTextarea, WizSelect, WizNestedSelect, WizCheckbox, WizStaticCheckbox, WizDatepicker, WizAutocomplete, WizSearch, WizControlTemplates, WizFormErrors, WizErrorMessage | | Layout & shell | WizViewLayout, WizContainer, WizGrid, WizHeader, WizFooter, WizNavbar, WizNavigatorExpander, WizSwitcherExpander, WizPageTitle, WizBreadcrumbs, WizScrollHorizontal, WizTabs, WizMobileDropdown, WizTopBarSupportMenu | | Content | WizCard, WizCardSimple, WizCardTitle, WizTile, WizTileEmpty, WizStatisticCard, WizEntity, WizContentDisplay, WizDisplayLine, WizText, WizEmpty, WizCollapse, WizStatusBadge, WizTooltip, WizLogo, WizRoleIcon, WizOnlineStatusIndicatedText | | Feedback | WizAlert + WizAlertService, WizNotificationService, WizDeleteModal + WizModalService, WizLoadingSpinner | | Files & media | WizDisplayImg, WizUploadFileDragAndDrop, WizUserAvatar, WizUserAvatarUpload, WizDownloadFileService | | Theming & config | WizThemeService, WizThemeSwitcher, WIZ_THEME_CONFIG, WIZ_UI_CONFIG | | Utilities | WizEmptyValuePipe, WizSafeHtmlPipe, WizTemplateBinderDirective, WizPostalCodeMaskDirective, WizAbstractControlDirective, WizLayoutService, WizResponsiveService |

Browse them with live controls:

npx nx storybook ui

7. Localisation

The library contains no i18n runtime and does not depend on @angular/localize. Every user-facing string is an input with an English default, so apps pass their own:

<wiz-list-buttons
  addButtonLabel="Dodaj pozycję"
  backButtonLabel="Wróć"
  generateFileButtonLabel="Generuj plik" />

The same applies to WizUploadFileDragAndDrop (text, hint, fileExistsError), WizDeleteModal (titlePrefix, cancelLabel, confirmLabel) and WizUserAvatarUpload (text, hint).

Strings repeated on almost every screen don't have to be passed each time — set them once through WIZ_UI_CONFIG. Keys you leave out keep their English default, and inputs still override the config per instance:

// app.config.ts
import { WIZ_UI_CONFIG } from '@wizards-space/ui';

providers: [
  {
    provide: WIZ_UI_CONFIG,
    useValue: {
      emptyPlaceholder: '---', // wiz-entity, empty value
      backLabel: 'Wróć', // wiz-list-buttons, wiz-page-title back arrow
      moreActionsLabel: 'Więcej akcji', // wiz-page-title, actions menu on phones
      generateFileLabel: 'Generuj plik', // wiz-list-buttons
      homeLabel: 'Strona główna', // wiz-breadcrumbs, first item
      homeLink: '/app/dashboard', // wiz-breadcrumbs, first item
    },
  },
];

8. Setup checklist

  • [ ] @wizards-space/ui installed, peer dependency versions match
  • [ ] styles.less added to the build's styles, with includePaths set
  • [ ] fonts + icons registered in assets
  • [ ] WizThemeService.init() runs at bootstrap
  • [ ] brand layer written, added to styles after the library stylesheet
  • [ ] WIZ_THEME_CONFIG provided, with the brand layer appended and storageKey namespaced
  • [ ] WIZ_FILE_STORAGE provided — only if file/image components are used
  • [ ] WIZ_UI_CONFIG provided — only if the app is not in English
  • [ ] provideAnimations() + NzModalModule in root — only if modals are used
  • [ ] a component renders with the app's brand colour, in light and dark