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

@novi-ui/core

v0.6.0

Published

Behavior, accessibility, type contracts and token conventions for Novi UI. Ships zero styles.

Readme

@novi-ui/core

Novi UI の全テーマが共有する土台。スタイルを1行も持たない。

pnpm add @novi-ui/core

何を担い、何を担わないか

このパッケージの価値は、担わないものをはっきりさせている点にある。

担う

| # | 責務 | 具体物 | |---|---|---| | 1 | 公開 API の型契約 | NoviVariant / NoviSize / NoviColor / NoviRadiusVariantMap | | 2 | slot 語彙 | 29 契約(27 コンポーネント)分の slot 名と必須/任意の区別、NOVI_CONTRACTS | | 3 | 挙動フック | useImeSafeKeys(日本語入力の誤送信対策) | | 4 | トークン規約 | --novi-* の命名規則、base.css(reset / @layer / ライト・ダーク) | | 5 | 不安定 API の封じ込め | 上流の UNSTABLE_* を安定名で再公開 | | 6 | 契約テスト | テーマが slot 契約を守っているか自動検査する |

担わない

| 担わないもの | 理由 | |---|---| | DOM 構造 | 固定した瞬間、テーマが構造を変えられなくなる。それは美学差が出せなくなるということ | | スタイル | 1行でも漏れると、テーマがそれを上書きする戦いが始まる | | React Aria Components の再エクスポート | 上流の改善が届かなくなる | | Provider | AI が設置を忘れて壊れる。RSC の境界も汚れる | | 実行時のコンポーネントファクトリ | テーマが1本しかない段階で共通部分は分からない。想像で作った抽象は必ず外れる |

エントリ

| エントリ | 内容 | React ランタイム | |---|---|---| | @novi-ui/core | 型契約・slot 語彙・トークン語彙 | import しない(RSC 安全) | | @novi-ui/core/client | フック・Toast プリミティブ | 'use client' | | @novi-ui/core/testing | 契約テストスイート(dev 専用) | — | | @novi-ui/core/base.css | reset / @layer / トークン | — |

メインエントリが React を import しないことは CI で検査している。 混入するとバンドラが パッケージ全体をクライアント専用と判定し、型を import しただけで RSC が壊れる。

slot 契約

core は「コンポーネントを構成する部位の名前」だけを決め、JSX は決めない。

import { modalSlots, modalRequiredSlots, type SlotMap } from '@novi-ui/core'

// modalSlots        = backdrop | panel | header | title | closeButton | body | footer
// modalRequiredSlots = backdrop | panel | body

const slots: SlotMap<typeof modalSlots, (typeof modalRequiredSlots)[number]> = {
  backdrop: 'fixed inset-0 bg-[--novi-color-overlay]',
  panel: 'bg-[--novi-color-bg] border border-[--novi-color-border]',
  body: 'px-6 py-4',
  // header は任意なので省略できる
  // wrapper のような語彙外のキーはコンパイルエラー
}

テーマは必須 slot さえ描画すれば、順序も入れ子も要素種別も自由。 閉じるボタンをヘッダー右上に置くテーマと、フッターにフルワイドで置くテーマが、 同じ props・同じ slot 名で共存できる。

全テーマは slot に対応する要素へ data-slot="<名前>" を出力する。 これ1つで、テスト・視覚回帰・ユーザーの CSS 上書き・AI の構造理解がテーマ横断で成立する。

契約テスト

import { NOVI_CONTRACTS } from '@novi-ui/core'
import { testSlotContract } from '@novi-ui/core/testing'

testSlotContract({
  name: 'Modal',
  contract: NOVI_CONTRACTS.Modal,
  render: () => <Modal isOpen>本文</Modal>,
})

必須 slot の欠落と語彙外 slot を検出し、どの slot が問題かを名指しで報告する。

日本語入力

'use client'
import { useImeSafeKeys } from '@novi-ui/core/client'

const keyProps = useImeSafeKeys<HTMLInputElement>((e) => {
  if (e.key === 'Enter') submit()
})
return <input {...keyProps} />

変換確定の Enter が送信として誤発火する問題を潰す。 isComposing / keyCode 229 / compositionend 直後の3経路すべてに対処している。 どれか1つでは環境差を吸収できない。

スタイルの拡張

各テーマは tv() 定義を named export している。

import { buttonStyles } from '@novi-ui/raster'
import { tv } from 'tailwind-variants'

// slot ベースなので `slots` で足す。`base` は効かないので注意
const myButton = tv({
  extend: buttonStyles,
  slots: { root: 'uppercase tracking-widest' },
})

API の命名

独自命名を作っていない。 React Aria の慣習に従う。

| 概念 | Novi | よくある別名 | |---|---|---| | 無効化 | isDisabled | ~~disabled~~ | | 押下 | onPress | ~~onClick~~ | | 選択状態 | isSelected | ~~checked~~ | | 開閉 | isOpen / onOpenChange | ~~open / onChange~~ |

variant / size / color / radius の語彙は core が固定し、全テーマが全値を実装する。 テーマごとに variant 名が違うと、単一ドキュメント + テーマ切替という構想が崩れるため。

取りうる値は NOVI_VARIANTS などの定数と、そこから生成される llms.txt を見てほしい。 ここに書き写すと、語彙が増えたときに片方だけ古くなる。

ライセンス

MIT