@42-central/ui-react

中文文档
0.26.0发布于 2026/11/05UNLICENSEDTypeScriptES Module近 30 天下载 4571

React UI kit for 42 tools

原始使用文档
文档目录11 节
  1. Install
  2. Wire up the styles
  3. Theme (light/dark/system)
  4. Next.js (App Router / RSC)
  5. Vite (client-only SPA)
  6. Forcing or defaulting a color scheme
  7. Driving a .dark class too
  8. Disabling the color transition
  9. Cycling modes with an updater
  10. Subpath imports
  11. Links

@42-central/ui-react

npm version React 19 Tailwind v4 Ark UI 5 ESM only

Install · Styles · Theme · Imports · Live docs ↗


Code-themed, RSC-friendly, ESM-only UI kit for 42 Intra-Next.

Install

The kit is on the public npm registry as @42-central/ui-react. No credentials are needed:

npm install @42-central/ui-react @ark-ui/react react react-dom tailwindcss
pnpm add @42-central/ui-react @ark-ui/react react react-dom tailwindcss
yarn add @42-central/ui-react @ark-ui/react react react-dom tailwindcss
bun add @42-central/ui-react @ark-ui/react react react-dom tailwindcss

@ark-ui/react, react, react-dom (all React 19) and tailwindcss v4 are peer dependencies.

Consumer tsconfig.json must use "moduleResolution": "Bundler" (or "NodeNext").

Using Vite? Add @42-central/vite-plugin-theme too, for flash-free theme loading (see Vite).

Wire up the styles

The kit ships its Tailwind theme and palettes as plain CSS — there is no plugin to register. Pick one of two patterns in your global stylesheet.

Option A — opinionated (one line). Pulls in Tailwind, the theme, and a @source glob that scans the built kit:

@import "@42-central/ui-react/styles.css";

Option B — full control (recommended for app code). Import Tailwind and the theme yourself, and point @source at the installed package so production builds keep the classes used inside the components:

@import "tailwindcss";
@import "@42-central/ui-react/theme.css";
@source "../node_modules/@42-central/ui-react/dist/**/*.js";

Option A skips the @source declaration, so production builds may miss dynamic classes used inside the components — stick with Option B unless you have full ahead-of-time class coverage. For local monorepo development point @source at the source tree instead — see apps/docs/app/global.css for a working example.

Theme (light/dark/system)

useTheme() persists an explicit "light" | "dark" | "system" choice and keeps data-theme on <html> in sync — that attribute is the whole contract (the @custom-variant dark in theme.css above). Avoiding a flash of the wrong theme needs a script that runs before your app's JS does; how you deliver that differs by framework.

Next.js (App Router / RSC)

Render ThemeScript from a Server Component, as early as possible (e.g. the first child of <body>), and mark the root <html> suppressHydrationWarning (the script sets data-theme before React hydrates it):

// app/layout.tsx
import { ThemeScript } from "@42-central/ui-react/theme-script";

export default function Layout({ children }: LayoutProps<"/">) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeScript />
        {children}
      </body>
    </html>
  );
}
"use client";
import { useTheme } from "@42-central/ui-react";

export function ThemeToggle() {
  const { resolvedTheme, setMode } = useTheme();
  const next = resolvedTheme === "dark" ? "light" : "dark";
  return (
    <button onClick={() => setMode(next)}>
      {/* Which emoji is *visible* is CSS-driven (dark:), not resolvedTheme —
          a server can't know the visitor's real preference, so anything
          rendered from resolvedTheme is wrong in the raw server-rendered
          HTML until React hydrates. resolvedTheme is only used above, for
          the click handler's "what's next" logic. */}
      <span className="dark:hidden">☀️</span>
      <span className="hidden dark:inline">🌙</span>
    </button>
  );
}

Vite (client-only SPA)

There's no server render step, so a React ThemeScript component can't help — by the time React could insert a <script>, first paint has already happened. The script needs to land in index.html itself, genuinely blocking (not type="module", which Vite treats as deferred). Three ways to get it there, in order of preference:

1. @42-central/vite-plugin-theme (recommended). Injects the script into index.html at both vite dev and vite build time, so you never edit the HTML by hand:

// vite.config.ts
import { defineConfig } from "vite";
import { themeInitPlugin } from "@42-central/vite-plugin-theme";

export default defineConfig({
  plugins: [themeInitPlugin()],
});

Install and options are in the plugin README.

2. The static asset, copied into public/. @42-central/ui-react/theme-init.js is a prebuilt, dependency-free script. A hardcoded <script src="/node_modules/..."> only works in vite dev — Vite's dev server serves node_modules directly, but a production build doesn't copy or resolve raw node_modules paths, so that tag would 404 after a real build. Copy the file into your own public/ directory instead (Vite serves public/ verbatim in both dev and prod), e.g. via a small predev/prebuild script:

// scripts/copy-theme-init.mjs
import { copyFileSync } from "node:fs";
copyFileSync(
  import.meta.resolve("@42-central/ui-react/theme-init.js").replace("file://", ""),
  "public/theme-init.js",
);
<!-- index.html -->
<script src="/theme-init.js" data-default-color-scheme="dark"></script>

It's configured via data-* attributes on its own tag (no build step needed once copied):

AttributeMatches useTheme() option
data-storage-keystorageKey
data-default-color-schemedefaultColorScheme
data-force-color-schemeforceColorScheme
data-attributeattribute — comma-separated, e.g. "class,data-theme"

3. Manual paste, as a last resort (non-Vite bundlers, fully custom setups) — the literal output of buildThemeInitScript():

<script>
  (function () {
    try {
      var k = "ui-theme",
        m = ["light", "dark", "system"];
      var s = localStorage.getItem(k),
        v = null;
      if (s) {
        try {
          v = JSON.parse(s);
        } catch (e) {
          v = null;
        }
      }
      if (m.indexOf(v) === -1) v = "system";
      var d =
        v === "system"
          ? window.matchMedia &&
            window.matchMedia("(prefers-color-scheme: dark)").matches
            ? "dark"
            : "light"
          : v;
      document.documentElement.setAttribute("data-theme", d);
    } catch (e) {}
  })();
</script>

Regenerate this block (import { buildThemeInitScript } from "@42-central/ui-react") whenever you upgrade the package, in case its internals change.

useTheme() works identically after any of these — same hook, no Vite-specific API.

Forcing or defaulting a color scheme

defaultColorScheme overrides the fallback used when nothing is stored yet ("system" unless set). forceColorScheme pins mode/resolvedTheme to a fixed "light"/"dark" value and turns setMode into a no-op — useful for a page that must render one scheme regardless of the visitor's preference (e.g. a themed marketing page or an embed).

There's no provider holding this configuration in one place, so both options must be passed identically to every useTheme() call site and <ThemeScript> (or the pasted Vite script) — otherwise the pre-hydration script and the hook can disagree on first load. This isn't a gap from lacking a provider: Mantine, which does have one (MantineProvider), documents the exact same requirement for its own forceColorScheme — it "must be set both on MantineProvider and ColorSchemeScript," since the script always runs before any provider exists.

// app/layout.tsx
<ThemeScript forceColorScheme="dark" />
"use client";
const { resolvedTheme, setMode } = useTheme({ forceColorScheme: "dark" });
// resolvedTheme is always "dark"; setMode is a no-op while forced.

Driving a .dark class too

data-theme is the kit's own contract (theme.css's @custom-variant dark), but other CSS you don't control might key off a literal .dark class instead — Tailwind's default dark strategy, another component library, or (as in this repo's own docs site) Fumadocs' bundled styles. Pass attribute (mirroring next-themes' option of the same name) to have @42-central/ui-react drive that too, instead of hand-rolling your own mirror script/observer:

// app/layout.tsx
<ThemeScript attribute={["class", "data-theme"]} />
"use client";
const { resolvedTheme, setMode } = useTheme({
  attribute: ["class", "data-theme"],
});

Like storageKey/defaultColorScheme/forceColorScheme, attribute must be passed identically to both <ThemeScript> (or the pasted Vite script) and every useTheme() call site, for the same reason — there's no provider holding this configuration in one place. Passing attribute: "class" alone (no "data-theme") is supported too, if you genuinely don't need the kit's own contract for a given page — resolvedTheme reads the .dark class directly in that case.

Disabling the color transition

If any of your CSS transitions a color/background/border property, switching themes makes it visibly animate through the swap — so transitions are suppressed by default for one frame around every applied change (clicking a toggle, the OS preference flipping while in "system" mode, and regaining focus on a tab where the theme changed elsewhere). Pass keepTransitions (mirroring the option of the same name in Mantine) to opt back into normal transition behavior:

const { resolvedTheme, setMode } = useTheme({ keepTransitions: true });

Cycling modes with an updater

setMode also accepts a useState-style updater, resolved once against the current mode — useful for a single button that cycles through all three values instead of a two-way toggle:

const NEXT = { light: "dark", dark: "system", system: "light" } as const;
const { mode, setMode } = useTheme();
<button onClick={() => setMode((mode) => NEXT[mode])}>{mode}</button>;

Subpath imports

Tree-shake at the component boundary:

import { Button } from "@42-central/ui-react/button";
import { Modal } from "@42-central/ui-react/modal";
import { ThemeScript } from "@42-central/ui-react/theme-script";
import { cn } from "@42-central/ui-react/cn";

The package root (@42-central/ui-react) only re-exports shared utilities and types — cn, COLORS / Color, SIZES / Size, FormControlProps, useLogger, and useTheme / ThemeMode / ResolvedTheme. Components live at their own subpaths so bundlers never pull in the whole kit.

版本与发布记录

@42-central/ui-react 共发布 18 个版本,首次发布于 2026/10/08;过去 12 个月发布了 18 个版本。

版本发布日期距今
0.26.0latest2026/11/051 天前
0.25.02026/11/023 天前
0.24.02026/10/297 天前
0.23.02026/10/288 天前
0.22.02026/10/2710 天前
0.20.22026/10/2214 天前

关于 @42-central/ui-react 的常见问题

@42-central/ui-react 是什么?

React UI kit for 42 tools

如何安装 @42-central/ui-react?

在项目目录运行 npm install @42-central/ui-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.0.0)、react-dom(^19.0.0)、tailwindcss(^4.0.0)、@ark-ui/react(^5.38.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@42-central/ui-react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@42-central/ui-react 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@42-central/ui-react 可以商用吗?

@42-central/ui-react 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@42-central/ui-react 的下载量有多少?

根据 npm 官方下载统计,@42-central/ui-react 在 2026/10/05 – 2026/11/03 这 30 天内被下载 4,571 次,日均约 152 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@42-central/ui-react 还在维护吗?

最新版本 0.26.0 发布于 2026/11/05(1 天前);至今共发布 18 个版本,其中过去 12 个月发布了 18 个;首次发布于 2026/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。