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

@akms/ui-kit

v0.3.0

Published

Headless-ish menu/icon/field UI kit whose color & radius tokens chain off HeroUI 3's theme variables, so dark mode and custom themes carry across the kit and HeroUI components without extra wiring.

Downloads

862

Readme

@akms/ui-kit

Tailwind-based menu / icon component kit. Built for HeroUI 3 — the kit's color and radius tokens chain off HeroUI's bare theme variables, so dark mode and custom themes carry across the menu and HeroUI components without extra wiring.

Install

npm install @akms/ui-kit

Peer dependencies: react@^19, react-dom@^19, clsx, tailwind-merge, @iconify/react, @heroui/react@^3, @heroui/styles@^3, @akms/react-lib

Setup

1. Import preset.css into your Tailwind entry CSS (Tailwind v4)

Add it to the CSS file that has @import "tailwindcss" (your Tailwind entry):

@import "tailwindcss";
@import "@akms/ui-kit/preset.css";

preset.css does two jobs in one import:

  • defines the --uikit-* theme tokens (:root), and
  • declares @source "./lib/**/*.{js,cjs,mjs}" so Tailwind scans the kit's build output and generates the utility classes the components rely on.

⚠️ Import it through CSS @import in the Tailwind entry — not a JS/TS import "@akms/ui-kit/preset.css". A JS import pulls in the tokens, but Tailwind never processes the bundled @source, so the component utility classes are never generated and everything renders unstyled. You do not need a separate @source ".../@akms/ui-kit/lib/**" line — preset.css already carries it (with a ./lib/** path that resolves relative to the package, so it's location-independent).

2. Theming

The kit's tokens chain off HeroUI 3's bare theme variables (--foreground / --accent / --border / --overlay / --radius), so theme switching is HeroUI's job: toggle a class like .dark on <html> (or define your own [data-theme="..."] block) and the kit follows automatically along with HeroUI components. No provider needed.

Usage

MenuList

Scrollable side-menu container. Accepts only MenuButton, MenuGroup, MenuPopover, MenuSelector, and MenuLabel as children.

import { MenuList, MenuButton } from "@akms/ui-kit";

<MenuList>
    <MenuButton label="Home" startItem={{ icon: "tabler:home" }} />
</MenuList>

By default MenuList fills its parent's available height (flex-1 h-full), so a sidebar menu stretches to the bottom. The base classes are merged with your className via tailwind-merge, so you can override the fill — e.g. for a short back-link row that should size to its content instead of growing:

<MenuList className="flex-none h-auto min-h-0">
    <MenuButton label="← Back" startItem={{ icon: "tabler:arrow-left" }} onClick={goBack} />
</MenuList>

⚠️ For SSR hydration issues, wrap the tree on the consumer side (e.g. ClientOnly).

MenuButton

A clickable menu row.

<MenuButton
    label="Dashboard"
    startItem={{ icon: "tabler:layout-dashboard" }}
    isSelected
    onClick={() => navigate("/dashboard")}
/>

| prop | type | description | | --- | --- | --- | | label | string \| ReactNode | Center label of the row | | startItem | MenuSlot | Left slot (typically an icon) | | endItem | MenuSlot | Right slot (typically action buttons) | | isSelected | boolean | Selected state (primary color emphasis) | | isDisabled | boolean | Disabled state (overlay + click blocked) | | onClick | (e) => void | Click handler for the label area |

Putting action buttons in a slot

You can place interactive controls directly inside endItem.node. The inner label button and the slot containers live in separate grid columns, so there is no overlap — you do not need e.stopPropagation(). The row's hover / active background is also suppressed while a slot button is hovered or pressed, so the row doesn't visually compete with the action.

MenuIcon itself becomes a button when you pass onClick — that's usually the cleanest way to drop in icon-only actions:

<MenuButton
    label="Document"
    startItem={{ icon: "tabler:file-text" }}
    endItem={{
        node: <>
            <MenuIcon icon="tabler:edit" title="편집" onClick={() => openEdit()} />
            <MenuIcon icon="tabler:trash" title="삭제" onClick={() => openDelete()} />
        </>,
    }}
    onClick={() => navigate("/docs")}
/>

startItem hover toggle

Only startItem supports a hoverIcon pair. The icon swaps on row hover.

<MenuButton
    label="Download"
    startItem={{ icon: "tabler:download", hoverIcon: "tabler:download-off" }}
/>

MenuGroup

Expand/collapse group. startItem is auto-filled with a chevron icon, but an explicit prop overrides it.

<MenuGroup label="Settings" startItem={{ icon: "tabler:settings" }}>
    <MenuButton label="Profile" />
    <MenuButton label="Security" />
</MenuGroup>

MenuPopover

Same trigger row as MenuGroup, but clicking opens the items in a HeroUI popover flyout instead of expanding inline — built for sections with too many items to expand comfortably. The list scrolls inside the popover, so the item count never stretches the sidebar.

<MenuPopover label="All items" startItem={{ icon: "tabler:list" }}>
    <MenuButton label="Item 1" onClick={() => navigate("/1")} />
    <MenuButton label="Item 2" onClick={() => navigate("/2")} />
    {/* ...dozens more — they scroll inside the popover */}
</MenuPopover>

Add a search input at the top with searchable. The query matches item labels case-insensitively as a substring, and the popover height animates as results change (no jumpy resize between 0 and many results):

<MenuPopover label="Find item" startItem={{ icon: "tabler:list-search" }} searchable searchPlaceholder="이름으로 검색…">
    {items.map((item) => (
        <MenuButton key={item.id} label={item.name} onClick={() => navigate(`/${item.id}`)} />
    ))}
</MenuPopover>

| prop | type | default | description | | --- | --- | --- | --- | | label | string \| ReactNode | — | Trigger row label | | startItem | MenuSlot | — | Left slot (typically an icon) | | endItem | MenuSlot | direction caret | Right slot; defaults to a caret pointing the popover direction | | placement | HeroUI placement | "right top" | Which side the popover opens on (HeroUI 3 space-separated placement) | | closeOnSelect | boolean | true | Close the popover when an item inside is clicked. Set false when nesting expand/collapse groups inside | | searchable | boolean | false | Show a search input at the top and filter children by label substring (string labels only; non-string labels are hidden during search) | | searchPlaceholder | string | "Search…" | Placeholder text for the search input when searchable is on |

MenuSelector

Radio-style group that manages single selection internally.

<MenuSelector
    items={[
        { key: "ko", label: "한국어", startItem: { icon: "tabler:point" } },
        { key: "en", label: "English", startItem: { icon: "tabler:point" } },
    ]}
    onSelect={(key) => i18n.changeLanguage(key)}
/>

MenuLabel

Non-interactive section caption — also doubles as a static display row. Ships with built-in caption styling (font-semibold + top divider) so it visually separates sections without extra props.

<MenuLabel label="Language" />

{/* As a static display row — override the divider/bold via className if needed */}
<MenuLabel label="Version 1.2.3" startItem={{ icon: "tabler:info-circle" }} className="border-t-0 mt-0 pt-0 font-normal" />

MenuIcon

@iconify/react wrapper that picks up the ui-kit color tokens. By default it renders as a non-interactive <div> with pointer-events: none, sized to follow the kit's text scale. Passing onClick turns it into a <button> with a 28×28 hit-area, kit-token hover/active feedback, and the same press animation as MenuButton — handy for slot action icons.

import { MenuIcon } from "@akms/ui-kit";

{/* Decorative icon */}
<MenuIcon icon="tabler:home" />

{/* Icon button — becomes <button> when onClick is provided */}
<MenuIcon icon="tabler:edit" title="편집" onClick={() => openEdit()} />

| prop | type | default | description | | --- | --- | --- | --- | | icon | string | — | Iconify icon name ("tabler:home") or a single emoji character | | size | number \| string | "1.125rem" | Icon visual size — font-size for emoji, fontSize prop on <IconifyIcon> | | onClick | (e) => void | — | When set, renders as <button> with hit-area and press feedback | | title | string | — | Forwarded to the rendered <button> (use for accessible tooltip) | | isDisabled | boolean | false | Applies to button mode only — disables click and dims the icon |

Field

Form-row layout for settings / registration forms (Google-Play-Console style): a left label column and a right column holding an optional description (above the control), the control (children), and optional help text (below). Two columns on md+, stacked on mobile.

Text follows a three-tier emphasis so the row reads top-down: label at full strength (--uikit-foreground), description one step lighter (--uikit-foreground-secondary), and help faintest (--uikit-foreground-muted).

import { Field } from "@akms/ui-kit";

<Field
    label="이름"
    isRequired
    description="스토어에 표시되는 이름입니다."   // 컨트롤 위
    help="2~50자, 영문/숫자"                      // 컨트롤 아래
>
    <input className="..." />
</Field>

| prop | type | description | | --- | --- | --- | | label | ReactNode | Left-column label | | isRequired | boolean | Appends a * (danger color) after the label | | description | ReactNode | Secondary text above the control | | help | ReactNode | Secondary text below the control | | children | ReactNode | The control(s) in the right column |

Keep label plain — express "required" with isRequired, not by embedding markup in the label.

Theming

The kit's color and radius tokens chain off HeroUI 3's unprefixed bare tokens (--foreground / --accent / --overlay / --border / --radius, in oklch), so the kit tracks HeroUI's dark-mode toggle and custom theme automatically — toggle .dark on <html> (or define [data-theme="..."]) and both the kit and HeroUI components paint together. When HeroUI isn't loaded, HeroUI's light-theme defaults below apply as fallbacks.

Override any of these in your own CSS to retune the kit without touching HeroUI itself (cascading through standard CSS works fine — a :root override, a parent-class override, or an inline style on any ancestor).

| variable | follows / fallback | purpose | | --- | --- | --- | | --uikit-foreground | --foreground / oklch(0.2103 0.0059 285.89) | Base text | | --uikit-foreground-secondary | foreground 70% | Secondary text — Field description (above the control) | | --uikit-foreground-muted | foreground 55% | Faint text — Field help (below the control) | | --uikit-danger | --danger / oklch(0.637 0.237 25.331) | Danger / required-mark color (Field *) | | --uikit-primary | --accent / oklch(0.6204 0.195 253.83) | Selected / accent color | | --uikit-border | --border / oklch(90% 0.004 286.32) | Border | | --uikit-surface | --overlay / oklch(100% 0 0) | Floating surface (popover) background | | --uikit-radius | --radius × 3 / 1.5rem | Container corner radius (matches HeroUI's --radius-3xl tier — Popover / Button) | | --uikit-radius-row | --radius × 2 / 1rem | Menu-row corner radius (matches HeroUI's --radius-2xl tier — menu-item) | | --uikit-indent-step | 0.75rem | MenuGroup indent | | --uikit-state-hover | foreground 5% | Hover background | | --uikit-state-active | foreground 10% | Active background | | --uikit-primary-bg | primary 18% | Selected background | | --uikit-disabled-overlay | background 50% | Disabled overlay |

Full definitions are in preset.css.

Development

npm install
npm run build       # build lib/
npm run watch       # build on change
npm run playground  # run the RR7 SSR playground demo

playground/ imports the library's src/ directly via alias, so HMR + sourcemap debugging works out of the box.

Structure

src/         # library (published)
playground/  # React Router 7 SSR demo
lib/         # build output (published)

License

Proprietary — Copyright © 2026 Alkemic Studio. All rights reserved. See LICENSE.md.