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

@ryadavwebdev/ui

v0.2.0

Published

Design tokens and presentational Angular components shared across the portfolio ecosystem.

Readme

@ryadavwebdev/ui

Design tokens and presentational Angular components shared across the portfolio ecosystem (portfolio, folio, design-system).

Presentational only — no HTTP, no router, no app domain knowledge. Everything public is exported from src/public-api.ts; nothing is deep-imported.


1. Load the tokens once, globally

The design tokens are a stylesheet, not a TypeScript export. Every app loads it exactly once. It must come first, before the app's own global styles.

Installed from npm (portfolio, design-system) — in angular.json:

"styles": [
  "node_modules/@ryadavwebdev/ui/styles/tokens.css",
  "src/styles.css"
]

Inside this repo (form-builder, board) the libraries resolve to source, so point at the workspace path instead — and note it belongs on the esbuild target, which is where Native Federation moved the real build options:

"styles": [
  "projects/ui/styles/tokens.css",
  "projects/form-builder/src/styles.css"
]

Under the Native Federation host: a remote loaded into the host does not serve its own index.html, and the host's global styles win. The host must load tokens.css too, or federated remotes render unstyled.

Once loaded, components reference the custom properties and never hardcode a value:

.thing {
  padding: var(--space-16);
  border-radius: var(--radius-md);
  background: var(--color-surface-elevated);
  color: var(--color-text);
}

2. Switching themes

Themed tokens are declared once with CSS light-dark(), which resolves against the element's computed color-scheme. That covers both requirements with one mechanism:

| <html> | Result | | -------------------- | ------------------------------------- | | no attribute | Follows the OS prefers-color-scheme | | data-theme="light" | Pinned light, overriding the OS | | data-theme="dark" | Pinned dark, overriding the OS |

light-dark() is Baseline: Chrome 123+, Safari 17.5+, Firefox 120+.

Drive it from @ryadavwebdev/core, which owns the state and the persistence:

import { ThemeService } from '@ryadavwebdev/core';

export class Shell {
  protected readonly theme = inject(ThemeService);
  // theme.isDark()  theme.toggle()  theme.set('dark')  theme.useSystem()
}

Or call the framework-free helpers directly:

import { setTheme, applyStoredTheme } from '@ryadavwebdev/core';

setTheme('dark'); // sets <html data-theme> and persists
applyStoredTheme(); // re-applies the saved choice

Avoiding a flash of the wrong theme. ThemeService can only run once Angular has booted. Add this to index.html so the attribute is set before first paint (the key is `${storagePrefix}.theme`, default prefix folio):

<script>
  (function () {
    try {
      var theme = localStorage.getItem('folio.theme');
      if (theme === 'light' || theme === 'dark') {
        document.documentElement.setAttribute('data-theme', theme);
        document.documentElement.style.colorScheme = theme;
      }
    } catch (error) {}
  })();
</script>

Both lines are required — the color-scheme one is not optional. Angular's critical-CSS inliner drops :root[data-theme="light"|"dark"] from the inlined block, because those selectors match nothing in the served HTML (Angular has not run yet). Those rules therefore arrive with the deferred stylesheet, after first paint. Set the attribute alone and the first paint falls back to the OS preference, then visibly flips — the exact flash this snippet exists to prevent. An inline style outranks any stylesheet and applies immediately.

3. Token reference

| Group | Tokens | | ------------- | ------------------------------------------------------------------------------------------------------------------- | | Surfaces | --color-bg --color-surface --color-surface-elevated --color-border --color-border-strong | | Text | --color-ink --color-text --color-text-secondary --color-text-muted | | Brand | --color-primary --color-purple --color-blue — each with -hover, --color-on-*, -text, -soft | | Semantic | --color-success --color-warning --color-danger --color-info — each with --color-on-*, -text, -soft | | Gradient | --gradient-brand --color-on-gradient | | Type | --font-display --font-sans --font-mono; --text-12…--text-48; --leading-*; --weight-*; --tracking-wide | | Space | --space-4 8 12 16 20 24 32 40 48 64 (4px base, named by px) | | Radius | --radius-sm md lg xl full | | Elevation | --shadow-sm --shadow-md --shadow-lg | | Focus | --focus-ring-width --focus-ring-offset --focus-ring-color | | Motion | --dur-fast --dur-base --ease-standard |

Naming convention for colour:

  • --color-x — the fill or accent
  • --color-on-x — text that sits on that fill (AA verified)
  • --color-x-text — the hue used as text on a background (darkened where needed)
  • --color-x-soft — low-alpha tint for ghost and hover backgrounds

Fonts

Inter, Space Grotesk and JetBrains Mono are fetched from Google Fonts by an @import at the top of tokens.css, so they travel with the stylesheet and need no index.html step. Every font token carries a full system fallback stack, so the UI degrades cleanly if the request fails.

4. Accessibility

Contrast is verified, not assumed. Every pairing below was computed against WCAG 2.1 (4.5:1 normal text, 3:1 non-text/interactive boundaries), light / dark:

| Pairing | Light | Dark | | -------------------------------------- | --------------- | ------- | | ink on bg | 19.64:1 | 18.82:1 | | text-secondary on bg | 7.73:1 | 7.66:1 | | text-muted on bg | 4.83:1 | 6.60:1 | | on-primary on primary | 4.70:1 | 6.07:1 | | on-purple on purple | 5.70:1 | 7.22:1 | | on-blue on blue | 5.17:1 | 7.73:1 | | on-success on success | 5.96:1 | 11.27:1 | | on-warning on warning | 6.17:1 | 11.77:1 | | on-danger on danger | 4.83:1 | 7.10:1 | | on-gradient on both gradient ends | 4.70:1 / 5.70:1 | — | | focus-ring-color vs background (3:1) | 5.17:1 | 7.73:1 | | border-strong vs background (3:1) | 3.80:1 | 3.73:1 |

Two of these drove real decisions:

  • White text fails on success (3.30:1) and warning (3.19:1). Rather than change the hues, --color-on-success / --color-on-warning are dark ink, which passes comfortably. Used as text on a light background those same hues also fail, so --color-success-text / --color-warning-text are darkened (#15803D, #B45309, both 5.02:1).
  • --color-border is decorative (1.27:1) and must never be the only boundary of a control. --color-border-strong exists for that — it is what the secondary (outline) button uses.

Focus. tokens.css applies a 2px ring at 2px offset in brand blue to every interactive element via a zero-specificity :where() rule, so components inherit it rather than each reinventing one.

Motion. Under prefers-reduced-motion: reduce the duration tokens collapse to 0ms at source, so nothing needs to opt out individually. A blanket safety net covers anything animating without the tokens.

5. Components

| Export | Selector | Notes | | --------------- | ------------------- | ------------------------------------------------------------------- | | Button | ui-button | variant · size · disabled · loading · block · ariaLabel | | Card | ui-card | heading · subheading · [card-header] / [card-footer] slots | | TokenShowcase | ui-token-showcase | The living reference — every token and component state |

Button variants: primary (brand red), secondary (outline), ghost, gradient (the signature hero variant — one per view at most), danger. Sizes: sm md lg.

The showcase

TokenShowcase renders the palette, type scale, spacing, radii, elevation and every component state on one page. Mount it on a route:

{
  path: 'design',
  title: 'Design system',
  loadComponent: () => import('@ryadavwebdev/ui').then((m) => m.TokenShowcase),
}

In this repo it is live at :4201/design and :4202/design. It is purely presentational — it holds no theme state, so it re-paints from the tokens when the shell's theme toggle flips data-theme.

6. Versioning

The public API is a semver contract — and for this package the tokens are part of that contract, not just the TypeScript exports. Renaming a custom property breaks every consumer's stylesheet exactly as loudly as renaming a component.

| Change | Bump | | -------------------------------------------------------------------------------------------------------------- | --------- | | Adding a component, an optional input, a new token, a new variant | minor | | Removing or renaming an export or a token; changing a type incompatibly; making an optional input required | major | | Bug fix, docs, a token value corrected without renaming it | patch |

While the package is 0.x, a breaking change bumps the minor — that is the standard 0.x convention, and it is why the API is not yet declared stable.

Bump projects/ui/package.json before every publish. npm versions are immutable: a published version can never be re-uploaded, only deprecated.

Deep imports are not supported. The package exports map exposes the root entry point plus ./styles/* only, so @ryadavwebdev/ui/lib/... fails at build time by design.

7. Build, test, publish

ng build ui          # → dist/ui (tokens.css ships at dist/ui/styles/)
ng test ui --watch=false
cd dist/ui && npm publish --access public

Publish from dist/ui, never from projects/ui — the built folder is the real package (it has the FESM bundle, the flattened .d.ts and the generated exports map).