Imported from manti-ui/ui (
AGENTS.md). Install upstream withnpx skills add manti-ui/ui. Copyright stays with the author.
Repository Guidelines
Rules (always apply)
These rules are mandatory for every agent working in this repo. The identical set
lives in CLAUDE.md — keep the two copies byte-for-byte in sync.
- Keep CLAUDE.md and AGENTS.md synchronized. Whenever
CLAUDE.md(Claude's memory) is changed, mirror the same change intoAGENTS.md, and vice versa, so all agents read one shared source of truth. Never let the two drift. - Design tokens are mandatory. When adapting any Zag.js component into Manti
UI or authoring a new Manti UI component, every visual value — color, spacing,
radius, typography, motion, elevation, z-index — must come from the design
tokens (
@manti-ui/tokens/ token-backed--manti-*and--variant-*CSS variables). Never hard-code raw values (hex colors, px sizes, magic numbers); if a token is missing, add it to@manti-ui/tokensfirst, then consume it.@manti-ui/tokensis the single source of truth: its primitive ramps and scale values are generated into the--manti-*custom properties inpackages/styles/src/tokens.css(the@tokens:generatedregion) bypnpm gen:tokens. Never hand-edit that region; after changing the contract, regenerate it — the styles build fails if it is stale. The theme-aware roles (light-dark()surfaces/text/elevation/panel) and the--variant-*vocabulary below the region stay hand-authored. Tokens form three tiers: primitive ramps → semantic roles/variants → component tokens (--manti-{component}-{property}, public and semver-stable, each defaulting to a semantic token). When a component needs an independent structural value (radius, padding, sizing, gap, typography), expose it as a component token rather than a bare literal or a private knob; keep only derivedcalc()values as private--_*. Register every component token in thecomponentTokensmap of@manti-ui/tokens. - Color-scale roles and interaction progression are mandatory. Primitive
ramps are ordered
1–12, but components must consume semantic roles or the--variant-*vocabulary rather than picking primitive stops directly. Use1–2for canvas/subtle surfaces;3for component rest,4for hover and keyboard highlight,5for pressed/selected/checked;6for quiet chrome,7for interactive borders and low-emphasis controls,8for focus rings and strong interactive chrome;9for solid fills,10for solid hover;11for supporting text and12for high-contrast text. Input-like controls (including Input, Textarea, Select, Combobox, NumberInput, PinInput, TagsInput, Editable, DatePicker, TimePicker, ColorPicker, and Clipboard) use--manti-borderat rest, strengthen to the neutral--manti-border-strongon hover, and use--variant-ringwhile active, focused, or open. The primary color must begin at active/focus/open for this control family; never use a variant color for its resting or hover border. Select and Combobox are the deliberate open-state exception: a connected trigger and its popup are one surface with one outline, so the trigger keeps the neutral--manti-borderwhile open instead of taking--variant-ring, and only the edge it shares with the popup is dropped. Zero the width on that seam side alone and tint it to the panel, so neither element paints a second line along the join; the popup does the same on its own seam edge. Which edge is the seam depends ondata-side, so never zero a border width unconditionally on open. Semantic mappings must be theme-aware: interaction strength increases in the direction that gains contrast in each theme. Filled and selected surfaces follow rest3→ hover4→ active5; solid controls follow9→10. Define these mappings centrally inpackages/styles/src/tokens.css, consume only semantic/variant roles in component CSS, and verify changes withpnpm check:color-scale,pnpm --filter @manti-ui/styles check:contrast, and the styles build. - Size travels with the surface a control opens.
sm | md | lgis one shared rhythm, resolved from the--manti-size-*channel inpackages/styles/src/size.css; component tokens default to it rather than to bare values. A popup is portalled out of its control, so the adapter must re-stampdata-sizeon the positioner: asmSelect opens asmlistbox, algMenu lays outlgrows. Never let a sized control open a fixed-size surface, and never open the channel on a component that spendsdata-sizeon something else (Dialog width, Avatar diameter, DataTable density). - Typography flows through Text and Heading. The shared type scale is the
token set in
@manti-ui/tokens(--manti-text-*,--manti-weight-*,--manti-leading-*,--manti-tracking-*), rendered by theTextandHeadingcomponents. In stories, demos, docs pages, and every other authored surface, never write a raw<h1>-<h6>or<p>; render copy withHeading/Text(the polymorphicasprop covers spans, labels, and other elements). Inside a component adapter, an element that is an anatomy part (it carries the machine'sdata-scope/data-part, like Dialog's title) must stay a native element, becauseText/Headingstamp their owndata-scope/data-partand spenddata-sizeon the type scale, which collides with the part-override contract and the size channel of rule 4. Such parts instead compose their look in CSS strictly from the typography tokens above: no raw font sizes, weights, line heights, or letter spacings anywhere in component CSS. When a needed stop is missing, extend the contract in@manti-ui/tokens(then runpnpm gen:tokens) instead of hard-coding a value. - Never use box shadows.
box-shadowis prohibited everywhere in Manti UI, including components, utilities, documentation surfaces, examples, motion states, and focus treatments. Do not add shadow tokens or shadow-based elevation. Communicate hierarchy with surface contrast, borders, spacing, and layering instead; use an outline only when it conveys a real boundary, focus state, or functional mask rather than simulated elevation. - Match the user's language. Always reply in the same language the user wrote their prompt in (e.g. Turkish prompt → Turkish answer). This applies to chat responses only; code, identifiers, comments, and docs stay in English.
Project Structure & Module Organization
This is a pnpm workspace for a framework-agnostic design system powered by
Zag.js behavior machines.
packages/tokens/: shared design-token contract.packages/styles/: shared CSS and state selectors.packages/folds/: framework-agnostic Zag.js behavior.packages/react/: React renderer and Storybook stories.packages/oklava/: the drop-in theme devtools panel..storybook/: Storybook (react-vite) configuration — the single dev surface and visual gallery.design/logo-explorations/: non-production brand exploration assets.docs/: architecture and product vision.
Keep component stories beside their implementation when practical. Export
public React APIs through packages/react/src/index.ts.
Build, Test, and Development Commands
Use Node >=22.12.0 and pnpm 10.
pnpm install # Install all workspace dependencies
pnpm dev # build packages, then run Storybook at localhost:6006
pnpm storybook # Run Storybook only, no rebuild (use after a pnpm dev build)
pnpm build # Build the packages and Storybook (storybook-static/)
pnpm build:storybook # Generate storybook-static/
pnpm lint # Run ESLint
pnpm typecheck # Check packages and stories
pnpm verify # Run lint, typecheck, and all production builds
pnpm gen:tokens # Regenerate --manti-* CSS vars from the token contract
pnpm gen:presets # Regenerate the shipped theme presets in styles/src/themes
Run pnpm verify before opening a pull request.
Coding Style & Naming Conventions
Write strict TypeScript and React function components. Prettier enforces semicolons, single quotes, trailing commas, and two-space indentation. Run pnpm format after broad edits.
Use:
PascalCase.tsxfor React components.camelCasefor functions and variables.*.stories.tsxfor Storybook stories.- semantic CSS names and tokens rather than raw product-specific colors.
Keep Zag.js machines framework-agnostic and adapters thin. Public components require typed exports, keyboard support, visible focus states, and meaningful screen-reader semantics.
Testing Guidelines
No unit-test framework or coverage threshold is configured yet. Every public component must include Storybook coverage for variants, disabled/error states, and meaningful interactions. Use the Storybook accessibility panel for WCAG checks and verify integration behavior through the component's stories.
When automated tests are introduced, colocate them as ComponentName.test.tsx.
The backlog
backlog/ holds frozen source for components that were built, then shelved
(QrCode, Timer, Swipe). Each entry mirrors the component's original repo paths
and carries a README.md with the reason plus a re-registration checklist. It is
outside every build, tsconfig, ESLint, Prettier, and Storybook glob — nothing
there is checked by pnpm verify, and it is expected to drift. docs/zag-coverage.md
marks these 📦 (done, then shelved), never ⬜ (todo). Read backlog/README.md
before re-adapting any of them, and shelve rather than delete a component.
A shelved component does not mean its behavior is unused: folds/swipe still
ships because Toast's swipe-to-dismiss is built on the core directly. Do not
remove a folds primitive on the grounds that no component renders it — check
consumers first.
Commit & Pull Request Guidelines
This workspace currently has no readable Git history. Until a project convention is established, use concise Conventional Commit messages, for example feat(button): add loading state or docs: clarify token naming.
Pull requests should explain the behavioral change, list verification commands, link relevant issues, and include Storybook screenshots for visual changes. Avoid committing generated dist/, storybook-static/, or cache directories.