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

@norstat/ui

v0.2.1

Published

Norstat's shared Vue 3 component library: form controls, feedback, layout and design tokens as one ES/CJS module with types and a single stylesheet.

Readme

@norstat/ui

Norstat's shared Vue 3 component library. It ships framework-agnostic building blocks (BaseButton, BaseInput, …) and composables as a single ES module with rolled-up type declarations and one stylesheet driven by CSS custom properties.

  • Stack: Vue 3 (<script setup>, Composition API), TypeScript, Vite library mode
  • Zero runtime dependencies — Vue is a peer dependency (^3.4.0)
  • Distribution: @norstat/ui on npm, published under a proprietary licence for use by Norstat and its affiliates (see LICENSE)
  • Every component is prefixed Base to avoid collisions in consuming apps

What's in the box

Twenty-five components, grouped the same way as in Storybook and in src/index.ts, plus one composable. Every prop, emit and slot is documented in Storybook (pnpm storybook); this list is only the map.

Form controls

  • BaseButton — call to action; renders an <a> when href is set. Variants primary, gradient, accent, secondary, ghost, text, danger; loading, full, icon slots.
  • BaseIconButton — icon-only button with a required accessible label.
  • BaseInput — text field with label, hint, error, prefix/suffix, sizes md/sm.
  • BasePasswordInput — BaseInput with a show/hide toggle.
  • BaseSelect — options array in the same field skin. Custom listbox on desktop (keyboard, type-ahead, flips above when there is no room, token-styled hover and selected states); native <select> on touch devices, or everywhere with native.
  • BaseCheckbox — labelled checkbox with hint and error.
  • BaseChip / BaseChipGroup — toggle chip and a single-choice group of chips (v-model).
  • BaseOtpInput — one-time code split into digit boxes; emits complete with the full code.

Feedback and overlay

  • BaseAlert — inline message with variant tone, live-region role, optional dismiss.
  • BaseStatusIcon — tinted circle around a status icon (neutral, success, warning, danger).
  • BaseProgressBar — determinate progress bar.
  • BaseStepDots — segmented step indicator (total, current).
  • BaseStrengthMeter — password strength segments driven by a score.
  • BaseLoadingDots — three-dot typing / loading indicator.
  • BaseOverlay — modal on a native <dialog>: Escape and optional backdrop close, body scroll lock, widths screen, form, full. Lives in the browser's top layer, so fixed third-party widgets (the Freshdesk Help launcher) are covered and inert while it is open.

Layout and content

  • BaseCard — surface with variants tint, solid, mint, accent, subtle, dark and radius lg/md.
  • BaseHeading — eyebrow + title + subtitle block with heading level, size, align, tone.
  • BaseBadge — small pill (primary, accent, neutral).
  • BaseStat — big figure with a caption; sizes md, lg, display.
  • BaseScreen — onboarding step shell: back button, step progress, primary and secondary CTA.
  • BaseAvatarBubble — artwork holder in circle, scene or bare shape.
  • BaseIconText — icon + text row, boxed or plain.
  • BaseLogo — Norstat mark (80×32) or outlined wordmark (140×72); has an intrinsic size, so size it with height only.
  • BaseFooter — footer shell with logo, tagline and a labelled <nav> of links; layout wrap (default) or row.

Composables

  • useDebounce(source, delayMs = 300) — read-only ref that mirrors source once it has been stable for delayMs:

    import { ref, watch } from 'vue'
    import { useDebounce } from '@norstat/ui'
    
    const query = ref('')
    const debouncedQuery = useDebounce(query, 300)
    watch(debouncedQuery, (q) => search(q))

Installing in an app

  1. Install the package from the public npm registry. No .npmrc or token is needed:

    pnpm add @norstat/ui
    # or: npm install @norstat/ui / yarn add @norstat/ui
  2. Make sure the app has the peer dependency. @norstat/ui does not bundle Vue and works with any vue@^3.4.0; the package manager warns if it is missing:

    pnpm add vue
  3. Import the stylesheet once in your app entry, then use components where needed:

    // main.ts
    import '@norstat/ui/style.css'
    <script setup lang="ts">
    import { ref } from 'vue'
    import { BaseButton, BaseInput } from '@norstat/ui'
    import type { BaseButtonVariant } from '@norstat/ui'
    
    const email = ref('')
    const variant: BaseButtonVariant = 'primary'
    </script>
    
    <template>
      <BaseInput v-model="email" label="Email" type="email" />
      <BaseButton :variant="variant" @click="submit">Save</BaseButton>
    </template>

Components are tree-shaken: an app that imports BaseButton and BaseInput bundles only those two. The stylesheet is one file for the whole library (tokens + every component, ~44 kB, ~7 kB gzipped) and is imported once.

The package ships an ES module (import) and a CommonJS build (require), each with its own type declarations, so it also works in CJS-only tooling such as Jest or older SSR setups:

const { BaseButton } = require('@norstat/ui')

Theming

All visual values are CSS custom properties prefixed --ns- (see src/styles/tokens.css). Override them on :root or any ancestor after importing the stylesheet:

:root {
  --ns-color-primary: #0a7d5c;
  --ns-radius-md: 0;
}

The stylesheet also ships one container theme, .ns-theme-light (src/styles/theme-light.css), for the white surfaces on the dark pages (the signup card, the thank-you panel). Put the class on the white container and every component inside switches to grey text and borders with a teal accent; nothing outside it changes:

<div class="ns-theme-light">
  <BaseInput label="Email" />
</div>

Every token is listed with its value under Foundations → Design tokens in Storybook.

The base font stack starts with SeroOffcPro, Norstat's licensed brand font. The package does not ship font files; apps that have the font load it themselves, everything else falls back to the system UI font. Override --ns-font-family-base to use a different typeface.

Browser support

The build targets Vite's baseline-widely-available set: the current and previous versions of Chrome, Edge, Firefox and Safari, concretely Chrome 107+, Edge 107+, Firefox 104+, Safari 16+ (desktop and mobile). Vue 3 itself requires native ES2015 (no Internet Explorer), and BaseOverlay uses the native <dialog> element, which all supported browsers ship. No polyfills are included.

Developing locally

Requirements: Node 22.12+ (.nvmrc) and pnpm 12 (corepack enable picks the pinned version from package.json).

pnpm install
pnpm dev              # empty playground at http://localhost:5173 for ad-hoc local testing
pnpm storybook        # Storybook at http://localhost:6006 — docs, controls, a11y checks
pnpm test             # Vitest + @vue/test-utils (jsdom); pnpm test:watch for watch mode
pnpm lint             # ESLint (flat config: eslint-plugin-vue + typescript-eslint); lint:fix to autofix
pnpm format           # Prettier (format:check is what CI runs)
pnpm typecheck        # vue-tsc for src/ + playground/ + .storybook/, tsc for vite.config.ts
pnpm build            # typecheck + Vite library build → dist/
pnpm build-storybook  # static Storybook → storybook-static/ (also run in CI)

Two surfaces render the components, with different jobs:

  • Playground (playground/) — an empty page for ad-hoc local testing. Drop the component you are working on into App.vue, iterate with hot reload, and clear the file before committing: nothing is stored there.
  • Storybook (.storybook/) — the documentation surface for designers and developers. Each component has a BaseThing.stories.ts next to it with controls for every prop, autogenerated docs from the types.ts JSDoc, and the a11y addon flagging violations. Stories are the browsable, shareable reference; the playground is scratch space.

Neither is part of the published package: files ships dist/ only, and the library build excludes *.stories.ts and .storybook/.

CI (.github/workflows/ci.yml) runs the same steps on every PR and push to main: install with a frozen lockfile, lint, format:check, typecheck, test, build and build-storybook. There is no hosted Storybook yet, so run pnpm storybook locally to browse it.

Adding a component

  1. Create src/components/BaseThing/ with four files, mirroring BaseButton:
    • BaseThing.vue — <script setup lang="ts">, withDefaults(defineProps<BaseThingProps>()), <style scoped> using only var(--ns-*) tokens
    • types.ts — exported prop/emit/variant types
    • BaseThing.spec.ts — at least one meaningful test (props, events, a11y attributes)
    • index.ts — re-exports the component and its types
  2. Re-export it explicitly from src/index.ts. Nothing is auto-globbed.
  3. Add BaseThing.stories.ts (copy the shape of BaseButton.stories.ts).
  4. Run pnpm changeset and describe the change for consumers.

The full checklist is in CONTRIBUTING.md.

Releasing

Versioning and the changelog are handled by Changesets; publishing to npm is triggered by a version tag.

  1. Every PR that changes the public package includes a changeset (pnpm changeset).

  2. On merge to main, the Release workflow opens (or updates) a "chore: version packages" PR that bumps package.json and CHANGELOG.md.

  3. Merge that PR, then tag the resulting commit and push the tag:

    git checkout main && git pull
    git tag "v$(node -p "require('./package.json').version")"
    git push origin --tags
  4. The Publish workflow (.github/workflows/publish.yml) runs on every v* tag: it installs, lints, typechecks, tests, builds and runs npm publish --access public. It authenticates through npm's trusted publishing (the package on npmjs.com trusts this repository, workflow file and the npm environment), so there is no token secret to store or rotate. The publish does not use --provenance: npm only accepts provenance attestations from public source repositories and this repository is private.

Before publishing, a package maintainer must configure Trusted publishing in the npm settings for @norstat/ui, selecting GitHub Actions with these exact, case-sensitive values:

  • Organization or user: Norstat
  • Repository: norstatpanel-ui
  • Workflow filename: publish.yml (not release.yml, and without .github/workflows/)
  • Environment: npm

The trusted publisher must allow direct npm publish, not only staged publishing.

An npm E404 on the publish PUT can mean missing publishing permission, not a missing package. Check the verbose publish logs for an OIDC token exchange failure and verify the trusted publisher settings above. If a new trust configuration expired before its first successful publish, recreate it on npm. Do not add NODE_AUTH_TOKEN or token-based .npmrc configuration to this tokenless workflow. Re-running an old Actions run uses the workflow from its original tagged commit, not this fix; ensure the release tag points to a commit containing the updated workflow before publishing.

The tag must match the version in package.json (v0.1.0 ↔ 0.1.0), otherwise the workflow fails before publishing. A version can never be published twice, and npm only allows unpublishing within 72 hours, so check the "Version Packages" PR carefully before merging it.

Manual publish (first release, or if the workflow is unavailable): npm login, then npm publish --access public from a clean main checkout. prepublishOnly runs lint, typecheck, tests and the build first.

Package layout

dist/
  norstat-ui.js       ES module, Vue externalised
  norstat-ui.cjs      CommonJS build, Vue externalised
  *.map               source maps for both
  index.d.ts          rolled-up type declarations (ESM)
  index.d.cts         the same declarations for the CJS entry
  style.css           tokens + all component styles

package.json exposes . (import → ESM, require → CJS, each with types), ./style.css and ./package.json. Only dist/, README.md, LICENSE and CHANGELOG.md are published; run npm pack --dry-run to see the exact file list.

Further reading

  • CONTRIBUTING.md — workflow, component checklist, conventions.
  • CHANGELOG.md — what each release changed for consumers.
  • LICENSE — proprietary; the package is public on npm so Norstat projects can install it without registry credentials, but it is licensed to Norstat and its affiliates only.
  • docs/signup-inventory.md — the norstatpanel_signup component inventory the library was ported from.
  • docs/campaigns-inventory.md — the norstatpanel-campaigns inventory and the design decisions behind the gradient / dark variants.