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

osp-ui-kit

v0.2.7

Published

Reusable React component library and design system for St. Peter Life Plan's Online Service Portal, built on Chakra UI v3.

Readme

osp-ui-kit

React component library and design system for St. Peter Life Plan's Online Service Portal, built on Chakra UI v3.

It ships six brand palettes, a full set of base primitives, and roughly 300 named exports — a data table, an app shell, form controls, overlays, charts and a PDF viewer among them. The visual language follows BootstrapMade NiceAdmin: navy ink on a pale blue canvas, 5px corners, and a soft navy card glow instead of hard borders.

Install

npm install osp-ui-kit

Two entry points are kept out of the main barrel because each pulls a dependency most consumers never need:

import { AppLayout, DataTable, BaseButton } from "osp-ui-kit";
import { AreaChart, LineChart } from "osp-ui-kit/charts";   // needs `recharts`
import { PdfViewer } from "osp-ui-kit/pdf-viewer";           // needs `react-pdf`

Setup

Wrap your app in StPeterProvider. It builds the Chakra system, loads the fonts, and installs every semantic token the components resolve against — nothing in the kit renders correctly without it.

import { StPeterProvider } from "osp-ui-kit";

export default function App({ children }) {
  return <StPeterProvider theme="niceadmin">{children}</StPeterProvider>;
}

theme accepts neutral (the default), green, pink, blue, orange and niceadmin.

Dark mode

Every semantic colour in the kit carries both a light and a dark value, and the swap happens in CSS. Components name tokens; they never ask which mode is active. That means dark mode is one provider away, and a component you write on top of the kit gets it for free as long as it uses tokens rather than literal colours.

Each palette keeps its own hue in the dark: a green-theme app becomes a deep green-black, niceadmin becomes the navy its #012970 headings hint at. There is no neutral-grey theme and no pure-black surface anywhere.

Enabling it

Add ColorModeProvider above StPeterProvider:

import { ColorModeProvider, StPeterProvider } from "osp-ui-kit";

export default function App({ children }) {
  return (
    <ColorModeProvider>
      <StPeterProvider theme="niceadmin">{children}</StPeterProvider>
    </ColorModeProvider>
  );
}

That is the whole integration. ColorModeProvider writes class="dark" onto <html>, and the token layer does the rest.

Reading and setting the mode

import { useColorMode } from "osp-ui-kit";

function Example() {
  const { colorMode, colorModePreference, setColorMode, toggleColorMode } = useColorMode();
  // colorMode           -> "light" | "dark"            (what is rendering now)
  // colorModePreference -> "light" | "dark" | "system" (what the user chose)
}

Use colorMode to branch on things that are not colours — an icon, a chart type, an image. For colours, name a token and let CSS resolve it; a JS branch cannot run before hydration, so the first paint would get the wrong one.

The toggle button

ColorModeToggle is a sun/moon icon button styled like the kit's other header actions. Drop it wherever you like:

import { ColorModeToggle } from "osp-ui-kit";

<ColorModeToggle />;

AppLayout and AppHeader can also place it for you:

<AppLayout navItems={navItems} showColorModeToggle>
  {children}
</AppLayout>

It is off by default, so an app that has not mounted ColorModeProvider never renders a control that appears to do nothing.

Three states

light, dark and system are all supported. system follows prefers-color-scheme and keeps following it — flipping the OS appearance while the tab is open updates the page live.

light is the default so that adding the provider to an existing app changes nothing until someone asks for dark. To follow the OS out of the box:

<ColorModeProvider defaultTheme="system">{children}</ColorModeProvider>

Persistence

The user's explicit choice is stored in localStorage under the key color-mode. Change it with storageKey, which matters when several apps share an origin and should not fight over one value:

<ColorModeProvider storageKey="osp-portal-color-mode">{children}</ColorModeProvider>

To pin one mode for a route — a print view, a marketing page — pass forcedTheme="light". It overrides both the stored value and the OS, and useColorMode reports the forced value.

ColorModeProvider accepts every next-themes ThemeProvider prop, so nonce (for a strict CSP) and the rest pass straight through.

Next.js App Router (SSR)

The server cannot read localStorage, so it always renders the default mode. next-themes closes the gap with a small blocking script that sets the class before first paint — no flash of the wrong theme. React needs to be told the class it finds is expected, which is what suppressHydrationWarning does:

// app/layout.tsx
import { ColorModeProvider, StPeterProvider } from "osp-ui-kit";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ColorModeProvider>
          <StPeterProvider theme="niceadmin">{children}</StPeterProvider>
        </ColorModeProvider>
      </body>
    </html>
  );
}

Three notes for App Router specifically:

  • suppressHydrationWarning goes on <html> and nowhere else. It suppresses the warning for that element's own attributes, not for your whole tree.
  • Both providers are client components. Mounting them in app/layout.tsx does not make your pages client components — children stay server components.
  • Anything that renders differently per mode must not render its mode-dependent output during SSR. ColorModeToggle handles this itself (it renders a same-size skeleton until mounted); do the same in your own components, or use a token so the branch happens in CSS instead of in JS.

The resolved mode is also mirrored onto the root's color-scheme, so native scrollbars, form controls and <select> popups go dark with the page.

Writing components that work in both modes

Use the semantic tokens. Never a raw Chakra palette step (gray.200, red.500) and never a literal hex — those are fixed values with no dark counterpart.

| Instead of | Use | | --- | --- | | gray.50, gray.100 | canvas (inset panel), primaryTint (hover fill, icon tile) | | gray.200 (border, track) | hairline, or hairlineSubtle for rules that repeat | | gray.300, gray.400 (icon, disabled) | muted | | gray.500, gray.600 (secondary text) | mutedStrong | | red.* / orange.* / green.* | danger / warning / success and their *Tint / *Fg | | white on a primary fill | primaryForeground | | white behind a dialog, menu or popover | surfaceRaised | | Chakra's bg, bg.panel, fg, fg.muted | canvas, surfaceRaised, foreground, mutedStrong |

Three that are not one-to-one:

  • Text on a tint takes the *Fg token, not the accent. In light, warning on warningTint is 1.68:1. The accent is for the dot or the glyph.
  • primary is not a text colour in light mode — 1.71:1 on the blue palette. For brand-coloured text use infoFg.
  • A hue the kit does not carry (a distinct purple, an orange between red and amber) should borrow Chakra's own semantic pairs — purple.subtle / purple.fg — which do have dark variants. Never a raw step.

Inline styles, gradients and SVG attributes cannot take Chakra props. Read the custom property directly, kebab-cased, and use color-mix for translucency:

stroke="var(--chakra-colors-hairline)"
style={{ background: "color-mix(in srgb, var(--chakra-colors-canvas) 92%, transparent)" }}

In props, Chakra's own alpha shorthand does the same thing: bg="surface/80".

Accessibility

Contrast in this kit is measured, not eyeballed. Dark mode was audited across all six palettes and all three planes (canvas, surface, surfaceRaised) — 318 text and UI pairs, every one at or above WCAG AA. Worst cases: heading 13.64:1, body foreground 10.09:1, mutedStrong 8.31:1, muted 5.36:1, primary as text 5.27:1, primaryForeground on primary 6.26:1, and every *Fg on its tint at 5.38:1 or better.

Reuse the existing pairings rather than inventing new ones.

Commands

npm run build                  # tsc -b, then vite build -c vite.lib.config.ts
npm run lint                   # oxlint
npm run test                   # Playwright Component Testing
npm run test:update-snapshots  # regenerate visual snapshots after an intentional restyle
npm run storybook              # development, documentation and design review
npm run build-storybook

npm run audit:contrast                # WCAG audit: 6 palettes x 2 modes x 3 planes
npm run audit:light-tokens:snapshot   # record light-mode token CSS before a change
npm run audit:light-tokens            # ...and prove it did not move after

The two audits read the palettes from source, so they cannot drift from what ships. Run them after touching any colour token.

Storybook is the development and design-review environment; there is no dev server and no src/App.tsx. The Theme and Color mode toolbar controls compose, so any story can be reviewed in all six palettes and all three modes. Getting Started/NiceAdmin Showcase is the reference page, and its Light and Dark story puts both modes side by side.

Type-aware lint rules are not enabled; turning them on needs oxlint-tsgolint and "typeAware": true in .oxlintrc.json.