Skip to content
Folio

Folio

v0.15.0

The White Reset

A pure-white monochrome design system for software, writing and photography. The ground is paper-white, the mark is near-black, and the only chroma left in the file is status — which is bound to state and never to brand.

Built onReactv19.2.8TypeScriptv6.0.3Tailwind CSSv4.3.3Radix UIRemix Iconv4.9.0
Components
115
Tokens
182
light and dark
Radius steps
5
one factor moves them all
Blurred shadows
0
depth is a hairline

Install

Shell
pnpm add @misoto22/design

Six runtime dependencies — Radix for the behaviour nobody should re-implement, cmdk for the combobox pattern, react-day-picker for the calendar, Remix Icon for icons, sonner for toasts, and clsx + tailwind-merge to resolve a class conflict in the caller’s favour. No router, no state library, no CSS-in-JS.

TSX
// 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>
  )
}

Foundations

Components

Already using Tailwind?

Take the portable token layers on their own and skip the second copy of the utilities. The mode is an attribute on <html>, so it can be written before the first paint and never flashes the wrong theme.

CSS
/* 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'] *));
HTML
<!-- 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">
TSX
// Every component merges your className LAST, through tailwind-merge — so a
// conflicting utility replaces the built-in one instead of racing it in the
// stylesheet.
<Button className="px-2">Tight</Button>
// → px-2 wins; the base px-6 is dropped, not emitted alongside it.

Reading this as an agent?

Everything here is written twice. The pages are for people; the rest is the same content for a reader that does not render CSS — every prop with its type, the keyboard contract and the whole example source, arranged for one sequential read.

If you have installed it

Shell
# One component in full — every prop with its type and default, the exported
# unions, the keyboard contract, the accessibility promises, the examples.
npx misoto22-design docs Button

# It resolves parts and types too, which is what you hold when an import fails.
npx misoto22-design docs CardBody

# Install the skill under .claude/skills/, and point AGENTS.md at it.
npx misoto22-design init --agents-md

The package documents itself, out of the version sitting in node_modules rather than whatever this site published last — which is the copy that is right when the two disagree. One component runs about 500 tokens, against tens of thousands for all 115.

The skill is the conventions an agent gets wrong from habit: the names that diverge from shadcn/ui, and colour that is never written as a raw class. Its name and description are around 110 tokens until something actually touches the package.

init writes the skill to .agents/skills/ — the path Codex, Cursor, Copilot, Gemini CLI, OpenCode, Cline, Zed and Warp share — and to .claude/skills/ when the project already has one. For any other agent, npx skills add Misoto22/misoto22-design finds it with no configuration.

If you only landed here

When you get it wrong

Some ways of misusing a component fail silently: a Field whose child is a wrapper puts the label on the box, an icon-only Button with no accessible name renders perfectly and is invisible to a screen reader. Documentation only helps a reader who went looking, and the problem is that nothing told them to look — so the component says it where it happens, with the field that caused it and an imperative fix. None of it reaches a production bundle.

  • ARTICLE_HTML_AND_CHILDREN
  • BREADCRUMB_CRUMB_NOT_LINKED
  • BUTTON_ICON_ONLY_UNNAMED
  • CLIPBOARD_WRITE_REJECTED
  • DIAGRAM_ACCENT_ON_CONTAINER
  • DIAGRAM_CELL_COLLISION
  • DIAGRAM_DIRECTION_ON_LEAF
  • DIAGRAM_DUPLICATE_ID
  • DIAGRAM_EDGE_DANGLING
  • DIAGRAM_LANE_UNKNOWN
  • DIAGRAM_REF_UNKNOWN
  • DIAGRAM_SPEC_MUTATED
  • DIAGRAM_STAGE_OUT_OF_RANGE
  • DIALOG_TITLE_MISSING
  • FIELD_CONTROL_NOT_LABELLABLE
  • FIELD_CONTROL_NOT_WIRED
  • REQUIRED_NAME_BLANK
  • SEARCHABLE_MENU_LABEL_UNREADABLE