基础
Getting started
Install it, pick a stylesheet, and render the first control.
Components ship compiled. You import them; you do not copy them into your project, and there is no CLI that writes a Button into your source tree for you to maintain. That is the trade the package makes: an upgrade is a version bump rather than a diff across forty files you now own.
Two peer dependencies, both React: react and react-dom at ^19.0.0. Everything else the components need — Radix, cmdk, sonner, lucide, tailwind-merge — is a real dependency and comes down with the package. Node 24 or newer, and the package is ESM only: the exports map carries an import condition and no require, so a CommonJS build will not resolve it.
The rest of this page is the part the README is thinnest on: which of the ten entries in the exports map you actually want, and what each one leaves out.
Read nextWorking with AI色彩
Install
One package. The styles are a separate import from the components — nothing pulls the CSS in on your behalf, and a project that imports only the components renders unstyled markup rather than an error.
pnpm add @misoto22/designThe CSS entry points
The choice is really between two recipes. styles.css is the whole compiled sheet: Tailwind, the primitives, the roles, the theming axes, the article styles, the keyframes, and the vendored @font-face rules appended to the end. One import, nothing else to decide, and Tailwind arrives with it.
The other recipe is for an app that already compiles Tailwind and does not want a second copy of the utilities. It is three imports — tokens.css, semantic.css, keyframes.css — and the thing worth knowing before you pick it is what those three do not carry.
data-mode="dark" and data-density="compact" are declared in tokens.css and survive. The other five axes — data-surface, data-radius, data-rules, data-type, data-motion — are declared only in themes.css, which is a separate export and is in neither the README recipe nor the snippet above. Write data-radius="sharp" without it and the attribute lands on the element and changes nothing. The faces are in fonts.css, also separate; the long-form article styles are in article.css. Add the ones you want by name.
- @misoto22/design
- The components, the types, `cn`, `CONTROL_BASE`, `CONTROL_BORDER`, `isInvalid`, `BRAND`.
- /styles.css
- Everything, compiled: Tailwind + every layer below + the vendored faces. The single-import path.
- /tokens.css
- The primitives, plus the default dark swap and the compact density axis.
- /semantic.css
- The roles a component actually reads — `--background`, `--foreground-muted`, `--border-color`.
- /keyframes.css
- The animations, and the one reduced-motion rule that stops them.
- /themes.css
- The five remaining theming axes. Not in the three-layer recipe; add it if you set any of them.
- /fonts.css
- The vendored @font-face rules. Already appended inside `styles.css`.
- /article.css
- Long-form prose styling, for the `Article` component.
- /tokens.json + /tokens
- The same tokens as data — JSON for a build script, and a typed `TOKENS` record with a `TokenName` union for TypeScript.
Tailwind interop
Semantic colours are deliberately not promoted to Tailwind colour utilities. They are consumed with the arbitrary-property syntax — text-(--ink), bg-(--paper), border-(--rule) — so adding or recolouring a role is one edit in semantic.css and nothing anywhere else. There is no bg-paper class and there is not going to be one.
If you compile your own Tailwind, point @source at the package’s dist so the utilities used inside a library component are generated for your build too, and declare the dark variant against the attribute rather than a class. The data-mode attribute goes on <html> on purpose: it can be written by an inline script before first paint, with no class list to reconcile and no flash of the wrong theme.
/* An app that already compiles Tailwind takes the token layers only, and
skips a second copy of the utilities. */
@import 'tailwindcss';
@import '@misoto22/design/tokens.css';
@import '@misoto22/design/semantic.css';
@import '@misoto22/design/keyframes.css';
/* Generate the utilities the library's own components use. */
@source '../node_modules/@misoto22/design/dist';
@custom-variant dark (&:is([data-mode='dark'] *));<!-- The mode is an attribute on <html>, so it can be written before first
paint and never flashes the wrong theme. -->
<html data-mode="dark">The first component
A labelled, required field and the button that submits it. Field does the ARIA wiring — aria-describedby, aria-required, aria-invalid — onto the single element child it wraps, which is why hand-rolling a <label>, an input and an error div is the one shape this system will not help you with. Two controls inside one Field wires neither, silently.
Errors go in Field’s error prop rather than beside its hint: they are one slot, not two stacked messages, and passing error already sets aria-invalid on the control — so do not also pass invalid.
// Once, at your app root — the compiled stylesheet carries the tokens,
// the utilities the components use, and the vendored faces.
import '@misoto22/design/styles.css'
import { Button, Field, Input } from '@misoto22/design'
export function SignIn() {
return (
<form className="flex flex-col gap-4">
<Field label="Email" required>
<Input type="email" />
</Field>
<Button type="submit">Continue</Button>
</form>
)
}