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

najm-kit

v2.1.48

Published

Reusable React UI component package for Najm framework

Readme

najm-kit

Reusable React component library for Najm applications. Provides themed UI primitives, hooks, and form components.

Install

bun add najm-kit tailwindcss @tailwindcss/postcss

Peer dependencies: react >=18, react-dom >=18. Requires Tailwind CSS v4 in the host app.

Optional peer dependencies: recharts, @tanstack/react-table, react-hook-form, @tanstack/react-query.

Styling — the entire setup

najm-kit is a Tailwind v4, shadcn-compatible library. PostCSS config (postcss.config.mjs):

export default { plugins: { "@tailwindcss/postcss": {} } };

Your global stylesheet — two imports, that's it:

@import "tailwindcss";
@import "najm-kit/theme.css";

This gives you every najm-kit component styled, dark mode wired (the .dark class), and a full token-backed palette you can use in your own markup too (bg-background, bg-card, bg-primary, text-muted-foreground, border-border, …).

Theming

najm-kit uses the standard shadcn token names (no prefix), so you rebrand by overriding CSS variables — or paste a theme straight from tweakcn / the shadcn registry:

:root { --primary: oklch(0.55 0.2 290); --radius: 0.75rem; }
.dark { --primary: oklch(0.70 0.18 290); }

Add your own extra colors alongside najm-kit's:

@theme { --color-success: oklch(0.7 0.18 150); } /* → bg-success, text-success */

Dark mode: toggle the dark class on <html> (or any wrapper):

document.documentElement.classList.toggle("dark");

Theme Provider (optional)

For scoped theming without writing CSS — useful for embedded surfaces. The provider is opt-in: with no props it injects nothing and your :root/.dark CSS owns theming.

import { NajmThemeProvider } from 'najm-kit';

// preset:
<NajmThemeProvider preset="dark-blue">{children}</NajmThemeProvider>

// or mode + accent:
<NajmThemeProvider mode="dark" accent="emerald">{children}</NajmThemeProvider>

// shadcn-style global radius scale:
<NajmThemeProvider radius="0.75rem">{children}</NajmThemeProvider>

// exact same radius for cards, tables, buttons, inputs, dialogs, etc.:
<NajmThemeProvider radius="0.75rem">
  {children}
</NajmThemeProvider>

rounded-full and rounded-none remain explicit, so avatars, pills, switches, and square variants keep their intended shape.

JSON theme settings

Store one theme object in a JSON file, local storage, or your settings API:

{
  "mode": "dark",
  "accent": "violet",
  "radius": "0.75rem",
  "appearance": { "borderWidth": "1px" },
  "tokens": {
    "primary": "oklch(0.62 0.2 290)",
    "primary-foreground": "oklch(1 0 0)",
    "sidebar": "oklch(0.18 0.02 290)",
    "chart-1": "oklch(0.70 0.20 40)"
  }
}

Load and apply it from the same settings state used by your theme editor:

import rawTheme from './theme.json';
import { NajmThemeProvider, parseNajmThemeConfig } from 'najm-kit';

const initialTheme = parseNajmThemeConfig(rawTheme);

function App() {
  const [theme, setTheme] = useState(initialTheme);

  return (
    <NajmThemeProvider config={theme}>
      <SettingsPage value={theme} onChange={setTheme} />
      {children}
    </NajmThemeProvider>
  );
}

Changing the state updates the complete theme immediately. Use stringifyNajmThemeConfig(theme) when persisting it, and parse settings loaded from an API or local storage with parseNajmThemeConfig before applying them.

Components

Import from najm-kit:

import { NButton, buttonVariants } from 'najm-kit';
import { Input } from 'najm-kit';
import { Card, CardHeader, CardTitle, CardContent } from 'najm-kit';
import { Dialog, DialogContent, DialogTrigger } from 'najm-kit';
import { DataTable } from 'najm-kit';
import { Form, FormInput, useNForm } from 'najm-kit';

Available Primitives

| Category | Components | |----------|-----------| | Actions | NButton, IconButton, toggleVariants | | Forms | Input, Textarea, Label, Select, Checkbox, RadioGroup, Switch, DateInput, FileInput | | Feedback | Alert, Badge, Progress, Spinner, Toast | | Layout | Card, Sheet, Dialog, Popover, DropdownMenu, Tabs | | Data | Table (NTable), StatCard, DetailList | | Overlays | Command palette, Tooltip, Toast |

Hooks

import { useKeyboard } from 'najm-kit';
import { useDelayedLoading } from 'najm-kit';
import { useClickOutside } from 'najm-kit';
import { useDebouncedValue } from 'najm-kit';
import { useInfiniteScroll } from 'najm-kit';
import { useSelection } from 'najm-kit';

Production Notes

  • Designed for dashboard/admin UIs in Najm-powered applications
  • Uses Radix UI primitives under the hood — accessible by default
  • All components are unstyled by default — apply buttonVariants(), badgeVariants(), etc. with Tailwind
  • Requires Tailwind CSS v4 in the host application (see Styling above)
  • CodeMirror components are optional peer deps — import from najm-kit/json only if needed

NTable responsive columns

NTable accepts an NTableColumnDef<T>[]. Each column's meta can carry:

  • visible?: boolean — app-owned eligibility gate. Defaults to true. Set this from your role / capability decision. Columns with visible: false are removed from headers, body cells, the loading skeleton, and the column-settings menu.
  • hiddenBelow?: "sm" | "md" | "lg" | "xl" | "2xl" — hide the table column below the chosen Tailwind breakpoint. The column remains visible at that breakpoint and above (mobile-first). Table view only.
import { NTable, type NTableColumnDef } from "najm-kit";

const columns: NTableColumnDef<Family>[] = [
  { accessorKey: "name", header: "Family account" },
  {
    accessorKey: "email",
    header: "Email",
    meta: {
      visible: can("families.email.read"),
      hiddenBelow: "lg",
    },
  },
];

Notes:

  • visible is application-owned eligibility, not an NTable role system. NTable never imports najm-auth or reads a session; convert your own role / capabilities to a boolean.
  • Omitting visible is the same as true.
  • hiddenBelow is table-only. Card view, JSON view, and custom modes ignore it. Cards must do their own capability gating inside renderCard.
  • Hiding a column is presentation only. The backend must still enforce the permission and privacy-project the field. Never rely on UI hiding to protect sensitive data.
  • The user-controlled column visibility menu (settings → Columns) keeps working independently. It can report a column as selected while CSS hides it below the configured breakpoint.
  • The columns the TanStack table receives are already filtered, so the settings menu will not list visible: false columns.

If you need to inspect or build your own effective column list, the same pure helper is exported as filterResponsiveColumns. The literal class map is also exported as hiddenBelowClasses, and resolveHiddenBelowClass(breakpoint) returns the class for a single breakpoint or undefined when no breakpoint is set.

NTable responsive cards, loading, and pagination

Responsive row actions are visible by default on phone, tablet, and coarse or non-hover pointers. Fine-pointer desktop layouts may reveal them on hover, but keyboard focus always reveals the action. Applications still decide which menu items exist through menu, onView, onEdit, and onDelete; visibility does not grant an action or replace server authorization.

When dynamicHeight is enabled, table and card loading skeletons measure the available body. Table rows use the same header/row geometry as dynamic page sizing, while cards measure the active grid columns, card height, and gap. The loading surface also follows the loaded bordered, design recipe, radius, border color, shadow, and classNames.content/classNames.cards contract.

Use cardPagination to choose pagination presentation whenever the effective rendered mode is cards:

  • { mode: "paged" } (the default) preserves existing pagination.
  • { mode: "all" } renders every row already supplied and hides the footer.
  • { mode: "load-more", ... } renders every supplied row and provides a guarded, keyboard-operable Load more/Retry control with polite loading, appended-result, and end-of-list announcements.

showPagination={false} remains an absolute presentation override and hides both numbered controls and Load more. In table mode, existing controlled and manual server pagination remains unchanged.

import { NTable, type NTableCardPagination } from "najm-kit";

const cardPagination: NTableCardPagination = {
  mode: "load-more",
  hasNextPage: query.hasNextPage,
  loadingMore: query.isFetchingNextPage,
  loadMoreError: query.isFetchNextPageError
    ? "The next page could not be loaded."
    : undefined,
  onLoadMore: () => query.fetchNextPage(),
  loadMoreLabel: "Load more",
  loadingMoreLabel: "Loading more...",
  retryLabel: "Retry",
  endLabel: "No more results.",
};

<NTable
  data={query.data?.pages.flatMap((page) => page.rows) ?? []}
  columns={columns}
  getRowId={(row) => row.id}
  renderCard={ResultCard}
  cardPagination={cardPagination}
/>

The application owns the query, cursor/offset, accumulated pages, cache invalidation, search/filter/sort semantics, authorization, and privacy projection. Najm Kit never imports React Query, calls an endpoint, invents a page size, or treats supplied rows as proof that every database row is loaded. Client sorting and filtering cover the rows currently supplied unless the application implements matching server-side behavior.

For a responsive screen that uses current-page data in desktop table mode and accumulated pages in card mode, keep those two query shapes in the application and pass the appropriate data. Crossing the <640px responsive-card breakpoint does not overwrite the user's chosen view, pagination position, sorting, filters, expansion, or row selection.