# Manti UI: full documentation > Manti UI is an open-source, framework-agnostic design system for React. > Component behavior comes from Zag.js state machines and every visual > value resolves from a three-tier design token contract. Source: https://manti.design. Short map for agents: https://manti.design/llms.txt. Every section below is one documentation page, in sidebar order, as its original Markdown. JSX component tags mark rendered demos. --- URL: https://manti.design/changelog/ Summary: Release notes for every published version of Manti UI, from the first 0.1.0 release onward, with the breaking changes called out per version. # Changelog Release notes for every version of Manti UI, newest first. Pick a release to read its full notes. --- # Manti UI URL: https://manti.design/ Summary: Manti UI is a framework-agnostic design system for React: 53 accessible components driven by Zag.js state machines and a three-tier token contract. --- URL: https://manti.design/changelog/v0.12.1/ Summary: A patch for Slider and Steps, which took their axis from an ancestor's data-orientation instead of their own. # v0.12.1 _Released 2026-09-14._ ## A part reads its own orientation Slider's track and range, and Steps' item and separator, sized themselves with a nested ancestor selector: ```css [data-orientation='vertical'] & { width: var(--manti-slider-track-size); height: 100%; } ``` That matches the part under _any_ ancestor carrying the attribute, not just its own machine's root, and plenty of components stamp it for their own reasons: Scroll Area, Splitter, Tabs, Toggle Group, Segmented Control. A horizontal slider placed inside a vertical Scroll Area therefore took the vertical geometry, and its track collapsed into a narrow column at the left of the control while the range and thumb stayed on the horizontal axis. Every one of these parts is stamped with its own `data-orientation`, so they now read that instead. Steps' list is the one exception, since the machine does not stamp it there: that rule keeps an ancestor form, qualified to the steps root rather than to a bare attribute any wrapper could satisfy. Nothing changes for a component that was already laid out correctly: the two axes resolve exactly as before when no outer element carries the attribute. --- URL: https://manti.design/getting-started/ Summary: Install Manti UI in a React app: add @manti-ui/react and @manti-ui/styles, import the stylesheet once, and render your first accessible component. # Getting Started Manti UI is an open-source, framework-agnostic design system for React: 53 accessible components whose behavior comes from Zag.js state machines and whose appearance comes entirely from a three-tier design token contract. This page takes you from an empty project to a rendered component in four steps. ## Install `@manti-ui/react` includes tokens and behavior. `@manti-ui/styles` is a peer dependency so your app controls when CSS loads. ## Import CSS Import the stylesheet once at the app entry: ```ts import '@manti-ui/styles/index.css'; ``` For Tailwind v4, use the [Tailwind guide](/guides/tailwind) instead. Existing applications with global resets should also read the [integration guide](/guides/integration). ## Render a component ## Set theme preferences Put preferences on `` or any container: ```html … ``` - `data-theme`: `light` or `dark`; omit it to follow the OS. - `data-motion`: `default`, `none`, or `full`. - `data-radius`: `none`, `sharp`, `default`, `round`, or `pill`. ## Next - [Browse components](/components) - [Learn the token model](/foundations/tokens) - [Customize with plain CSS](/guides/plain-css) --- URL: https://manti.design/changelog/v0.12.0/ Summary: Theme presets become palette-only, so adopting a theme for its color no longer resizes controls or rounds corners. # v0.12.0 _Released 2026-09-14._ ## Presets are a palette, and nothing else A shipped preset used to move more than color. Every one set `--manti-radius-factor` alongside `--manti-cool-hue`, and Graphite also carried a density: `--manti-space-1` plus the three control heights. Importing `themes/ocean.css` for its blue therefore rounded every corner in the app, and Graphite squared and compacted the layout on the way in. Shape and density are product decisions. A palette must not make them, so a preset now sets only the colors: - each base color expanded into the full `--variant-*` vocabulary, - the two branded roles no `[data-variant]` block can reach, `--manti-accent-fill` and `--manti-selection-*`, - and `--manti-cool-hue`, the single hue behind every gray, surface, border and text role. Swap Ocean for Forest and only the color changes. ## Breaking `MantiPresetDefinition` drops `radiusFactor` and `density`, and the generated stylesheets in `@manti-ui/styles/themes` no longer declare them. An app that liked the corner or the spacing its preset came with gets the shipped values back, and the fix is the one declaration that always owned the decision: ```css @import '@manti-ui/styles/index.css'; @import '@manti-ui/styles/themes/ocean.css'; :root { --manti-radius-factor: 1.4; } ``` The `data-radius` modes (`none`, `sharp`, `default`, `round`, `pill`) are unchanged and still retune a whole subtree. TypeScript code that read `presets.ocean.radiusFactor` or `presets.graphite.density` no longer compiles; those values are a theme's own choice now. --- URL: https://manti.design/changelog/v0.11.1/ Summary: A patch for single-line controls, with one shared leading token, a tighter control radius, honest Select and Combobox seams, and a steadier Checkbox. # v0.11.1 _Released 2026-09-13._ ## Select and Combobox seams stop painting twice A connected popup used to hide the shared edge by painting it `transparent`, which let the panel behind show through as a pale line and still reserved the border's width. Both components now zero the width on the seam side only: the trigger keeps the neutral resting border while open, the popup drops its own seam edge, and neither draws a second line along the join. The popup's outside corners resolve from the same clamped radius as the trigger, so the `round` and `pill` radius profiles can no longer produce a mismatched pair. ## One leading for single-line controls Controls that render a single line of text now share one vertical rhythm through a new token: ```css --manti-leading-control: 1.5; ``` It is declared once for Button, Badge, Tabs, Segmented Control, Select, Combobox, Field controls, Number Input, Pin Input, Date Picker, Time Picker and Color Picker, so no component invents its own value. Override it to retune every single-line control at once; `Text` and `Heading` keep their own type scale, and intentionally multiline surfaces are untouched. ## Checkbox no longer moves the row around it The checkbox indicator is hidden while unchecked, which emptied the control's box and moved the baseline the surrounding line was measured against. Checking the box therefore re-measured that line. It was visible in Data Table: ticking the select-all box shortened the header row by about a pixel and shifted every body row with it. The control is now centered against the parent's x-height, so checking is purely a repaint. ## Tighter control radius `--manti-size-control-radius` steps one stop tighter than the panel radius at `md` and `lg`, and the `round` radius profile's factor moves from `1.4` to `1.2`. Together they stop a default-size button from reading as a pill. Projects that preferred the looser curve can set the token or the radius factor back. ## Listbox follows its own variant Listbox items highlight and press on their variant's soft ladder instead of the neutral wash, pointer hover and keyboard roving focus land on the same surface, and a checked row holds the stronger rung so selection stays legible while the keyboard moves through the list. The cursor is now `default`, matching a listbox rather than a link. Date Picker's trigger also drops the browser's default button padding. --- URL: https://manti.design/changelog/v0.11.0/ Summary: Theme Studio presets become importable token and stylesheet packages, with per-preset contrast and interaction audits. # v0.11.0 _Released 2026-08-21._ ## Theme Studio presets are importable Theme Studio's starting points used to exist only as objects inside the studio. They are now part of the published packages, so a project can adopt a complete preset with imports instead of copying generated CSS: ```css @import '@manti-ui/styles/index.css'; @import '@manti-ui/styles/themes/ocean.css'; ``` `@manti-ui/tokens` exposes the preset definitions, while `@manti-ui/styles` ships `violet`, `ocean`, `forest`, `rose`, and `graphite` theme stylesheets. The default theme remains the no-op baseline and needs no additional stylesheet. Each preset carries its full visual contract: variant ramps, hue and radius settings, density where applicable, accent fill, and selection roles. ## Multiple presets on one page Projects that render more than one theme at a time can import `@manti-ui/styles/themes.css` and scope a preset with `data-manti-theme`: ```html
...
``` The theme layer is ordered after the token layer, so a preset wins over token defaults while consumer CSS outside the layers can still override it. ## Presets are audited like the base theme The styles build now generates and validates every preset. Contrast checks and the interaction-progression gate run per theme, catching a preset that loses the rest → hover → active or solid → solid-hover progression before it ships. --- URL: https://manti.design/changelog/v0.10.0/ Summary: Text and Heading put the type scale behind a component API, tracking becomes a token, Switch drops its variant prop, and Foundations moves to a hub page. # v0.10.0 _Released 2026-08-19._ ## Text and Heading The Typography category shipped only Blockquote, Code, and Kbd, so body copy and titles had no component at all. Anyone laying out text rebuilt the scale by hand, this documentation site included: its stylesheet redeclared the heading sizes and their tracking, and the only way to reach `--manti-text-muted` was an inline style. Both components now render real semantics from the shared scale. Text is a `

` unless `as` says otherwise; Heading is a real `h1`–`h6`. ```tsx Design tokens are mandatory Every visual value comes from the token contract. ``` ### Size carries the weight `size` takes a scale stop and, optionally, a weight after a slash: ```tsx // stop only // stop and weight ``` The two forms are not equivalent. Writing the stop alone emits no weight attribute at all, which leaves the decision to CSS. A consumer who decides that `lg` means semibold in _their_ system says so once, rather than repeating it at every call site: ```css [data-scope='text'][data-part='root'][data-size='lg'] { --manti-text-weight: var(--manti-weight-semibold); } ``` Spelling out `lg/regular` overrides that again locally. ### Rank and size stay independent Heading's `level` picks the element and a default stop; `size` overrides the stop without touching the element. A section can stay an `h2` for the document outline while laying out small, and a hero can be an `h1` at display size, without anyone reaching for a styled `

`. ```tsx Hero Eyebrow ``` Leaving the weight off keeps the stylesheet's default, which is bold at `level={1}` and semibold elsewhere. ### Two color axes `emphasis` walks the neutral ladder (`default`, `muted`, `subtle`, mapping to `--manti-text` and its two quieter roles) and answers how loud a line is. `variant` is the separate semantic axis on `--variant-text`. They are independent, so "quiet but red" is expressible; `variant` wins when both are set. Text also carries `align`, `truncate`, and `lineClamp`. ## Tracking is a token Optical letter-spacing had no token, so the literals were scattered: `-0.01em` on the docs headings, `0.02em`, `0.04em`, and `0.06em` on small label text across six component stylesheets. The contract gains a `letterSpacing` ramp, generated into `--manti-tracking-tighter | tight | normal | wide | wider | widest` and bound into the Tailwind bridge as `--tracking-*`. The stops are exactly the values already in use, so nothing moves visually. Text and Heading step through them automatically as `size` grows, since body tracking on a display size reads as loose. ## Switch drops its variant **Breaking:** `Switch` no longer accepts `variant`. A switch reports one boolean, so recoloring the on-state per call site said nothing the control did not already say, and every usage had settled on the default anyway. The checked track now always uses the primary channel. To recolor it, override the switch component tokens in CSS. ## Foundations moves to a hub page Foundations was a sidebar section competing with Components for the same vertical space while holding five mostly-read-once pages. It is now a landing grid reached from the top nav, leaving the sidebar to the reference material people navigate between. The docs also fix a long-standing leak: the prose stylesheet claimed every `h2`, `p`, `blockquote`, and `code` on the page, and it sits in a later cascade layer than the Manti component styles. Headings rendered inside a demo picked up the prose section divider, and Blockquote and Code were being silently overridden. Those rules now exclude component roots. --- URL: https://manti.design/changelog/v0.9.0/ Summary: Popups now open at the size of the control that opened them, Slider marks land on their values, panel hairlines run through every arrow, and Popover drops its arrow for a documented placement set. # v0.9.0 _Released 2026-08-08._ ## Size travels with the surface a control opens A `sm` Select used to open a listbox with `md` rows. The popup is portalled out of the control, so `data-size` on the root never reached it, and every popup rendered at one fixed size no matter what the trigger was set to. The same held for Combobox, and Menu, Listbox, DatePicker, TimePicker, and ColorPicker had no `size` at all. `sm | md | lg` is now one shared rhythm, resolved from a `--manti-size-*` channel in `packages/styles/src/size.css`: type, supporting type, icon, control height and padding, panel padding and gap, item padding, gap and radius, and the square cell used by calendar and time grids. ```tsx + {/* outlined — the default */} + {/* filled: no resting border, --manti-fill surface */} ``` ## Surfaces are flat The translucent panel treatment — `backdrop-filter`, the tint/rim/inset triad — is removed from Button, Badge, Alert, Collapsible and the whole field family. Blur is expensive to composite, it dropped out entirely on the `@supports` fallback path (so two users saw two different designs), and layered over a busy page it read as noise rather than depth. Those components now paint an opaque surface with a real border. - Solid controls keep their inner top highlight, but it is mixed off `--variant-on-solid` instead of a hard-coded white, so it stays legible on every variant. - Soft treatments (Button, Badge, Alert `secondary`) merge the border into the fill: the one-pixel footprint survives for stable sizing, without drawing a contrasting edge. - Blur-less `@supports` blocks are deleted along with the effect they guarded. ## The input border progression, applied The control family — Input, Textarea, Select, Combobox and friends — now follows one rule end to end: `--manti-border` at rest, `--manti-border-strong` on hover, `--variant-ring` once active, focused or open. Primary color starts at focus and never appears on a resting border. - **`fill` appearance.** `data-appearance="fill"` swaps the outlined control for a borderless `--manti-fill` surface that strengthens to `--manti-fill-strong` on hover and focus, then shows the ring on focus. - **Select and Combobox connect to their popup.** The trigger reads `data-connected="top" | "bottom"` from the positioner's resolved side and drops the matching corner radii and border edge, so trigger and listbox read as one shape. Closed or empty, the attribute is absent — no bottomless box on a focused-but-closed trigger. - Invalid states, the required marker, the error message and the caps-lock notice all move onto new semantic roles instead of reaching into the red and amber ramps directly. ## Components - **Collapsible** renders a state-aware chevron indicator by default — expand on closed, collapse on open. Pass `indicator` for your own node or `false` to suppress it; size it with the new `--manti-collapsible-icon-size` token. The trigger's padding tightens by one step. - **Menu** accepts `bottom-center` as a placement alias for `bottom`, typed as the new `MenuPlacement`. - **ColorPicker** pins its root to the primary variant so its ring matches the rest of the control family. - **Button**'s `sm` step tightens: `--manti-space-2` of inline padding and a radius factor of `0.8` rather than `0.7`. ## Tokens - **Semantic status roles.** `--manti-danger-icon`, `--manti-danger-border`, `--manti-danger-text`, `--manti-warning-text`, `--manti-accent-fill` and `--manti-contrast-light` replace the `light-dark(var(--manti-red-8), …)` pairs that had been inlined across component CSS. Both text roles are now audited by the contrast gate. - **`colorScaleRoles` is exported** from `@manti-ui/tokens` — the `1`–`12` contract (app background, component background, border, solid, text) as typed data rather than prose, and reachable as `mantiTokens.color.scaleRoles`. - **A color-scale gate runs in the styles build.** `check:color-scale` fails the build when component CSS names a raw color or a primitive ramp stop directly, and when a built-in variant assigns a role a stop outside its band (borders `6`–`8`, rings `7`–`8`, solids `9`–`10`, with the documented exceptions). Available standalone as `pnpm check:color-scale`. - **The `pill` radius preset is dropped; the channel stays.** `pill` shared `round`'s factor of `1.4` and differed only by raising `--manti-radius-pill`, which the parts that read as unmistakably pill are pinned past anyway — so on a real page the step showed no visible difference. Four presets remain, one per distinct factor: `none` `0`, `sharp` `0.6`, `default` `1`, `round` `1.4`. Nothing is lost, and raising the channel now composes with any factor: ```css :root { --manti-radius-pill: 9999px; } ``` ## Documentation - **`llms.txt`** ships at [manti.design/llms.txt](https://manti.design/llms.txt) — a condensed technical map of the install, the token tiers, the anatomy contract and the component surface, written for coding agents. The new _Working with AI_ page links and downloads it. - **The `size` prop is documented on the twelve components that shipped it without showing it**: Badge, Combobox, DataTable, Drawer, Pagination, PinInput, Progress, RatingGroup, Select, Spinner, Tabs and ToggleGroup. Each gets a `## Sizes` section, because the prop scales something different in each — control height, row density, panel extent, track thickness. - The repository guides (`README`, architecture, styling, design-system, token and coverage docs) are rewritten against the current token system, and every component page picks up the new variant vocabulary. --- URL: https://manti.design/changelog/v0.5.0/ Summary: Colour ramps are re-indexed 1-12 and audited with a contrast gate, secondary becomes amber, and radius splits size from shape with data-radius presets. # v0.5.0 _Released 2026-07-25._ ## Breaking changes - **Ramp stops are re-indexed `1…12`.** The `--manti-{ramp}-{50…950}` custom properties become `--manti-{ramp}-{1…12}`. The stops are perceptual positions on an OKLCH lightness backbone, not font-weight magnitudes, and an unbroken sequence makes "one stop darker" arithmetic instead of a lookup. Twelve rather than eleven because that is what the roles need: 1–2 backgrounds, 3–5 component backgrounds, 6–8 borders, 9–10 solids, 11–12 text. ```diff -background: var(--manti-orange-500); +background: var(--manti-orange-7); ``` The backbones were resampled 11 → 12 with monotone cubic (PCHIP) interpolation: endpoints and curve shape preserved, steps re-spaced, monotonicity guaranteed so no stop can come out lighter than the one before it. **Tailwind utility names are unaffected.** The bridge deliberately keeps its `50…950` names and maps them onto the new stops, so `bg-orange-500` still resolves to Manti. Renaming those keys would make the utility fall back to Tailwind's built-in palette — a wrong colour with no error. - **`secondary` is now amber.** It is structured like `primary` instead of being a neutral treatment, which changes the Badge and Alert defaults. Two consequences worth knowing: the emphasis ladder is no longer monochrome, and `secondary` shares the amber ramp with warning semantics. `tertiary` and `outline` are now the neutral treatments — reach for those where you relied on `secondary` being grey. ## Radius: size and shape are now separate One t-shirt ramp cannot serve both a 40px button and a 20px checkbox. A step that reads as "softened" on the button reads as "circle" on the checkbox, and sharing a step forces a choice between the two. - **Size — one factor rescales the ramp.** Every step but `full` is emitted as `calc( * var(--manti-radius-factor))`, so a single property retunes the whole system proportionally: ```css :root { --manti-radius-factor: 1.4; /* everything rounder, proportions preserved */ } ``` - **Shape — roundness is an opt-in channel, never a ramp step.** A part joins a channel with `max(, var(--manti-radius-pill))` — a no-op at the default, pill-shaped once a theme raises it. Control-class parts (button, input, select and combobox triggers, number-input, toggle, segmented control) join `pill`; switch and slider thumbs join `--manti-radius-thumb`. Checkbox joins neither, so it is structurally incapable of rounding into a radio no matter how round the theme gets — with no clamp involved, an explicit `--manti-checkbox-radius` still applies exactly as written. - **`[data-radius]` presets.** Set the attribute on any container to assign the factor and every channel at once: ```html
``` The five modes are `none`, `sharp`, `default`, `round` and `pill`, typed as `radiusModes` in `@manti-ui/tokens`. Components now anchor every size on **one** ramp step and scale it, rather than hopping to a coarser one — hopping is what makes a small control overshoot. Button's `sm`/`md`/`lg` are `0.7 / 1 / 1.3 ×` the `md` step (pixels unchanged at 10/14/18), so retuning `--manti-radius-md` moves all three together and touches nothing outside the control class. The docs right rail and the Storybook toolbar both ship a radius picker, so you can feel each preset before adopting one. ## Colour is now audited, not asserted - **A contrast gate runs in the styles build.** `check-contrast.mjs` audits every load-bearing pair with WCAG 2 and APCA and fails the build on a regression. Colour maths moved to [colorjs.io](https://colorjs.io), replacing ~150 lines of hand-rolled OKLCH/WCAG/APCA — the old clamp-to-sRGB was measuring the wrong colour for the 11 stops that sit outside sRGB. The audit also reports gamut coverage (11/72 outside sRGB, 0 outside Display-P3, max ΔEok 0.0106) and re-runs every gated pair through CSS Color 4 gamut mapping. - **The gate no longer passes what it cannot resolve.** A dangling `var()` used to print `?` and count as a success, which is how the primary and danger variants went unchecked after a rename. - **`--variant-on-solid` is generated by measurement.** `gen-variant-ink.mjs` resolves each variant's solid per theme, scores the candidate inks and writes the winner into a checked region. Ranking is AA-first then APCA Lc: on the primary orange the two metrics disagree (light ink scores Lc 65 but only 3.39:1; dark ink Lc 41 at 5.24:1), and AA is the floor the gate enforces. The generator reproduced all five hand-authored choices. ## Tokens derive from a base - Typography and spacing are derived from a single base value via `calc()`, so `--manti-text-base` or `--manti-space-1` rescales its whole family. - The neutral is unified on one cool hue (`--manti-cool-hue`, 280) — re-tint every surface, border and text role from one property. - Colour ramps are generated from compact specs (`rampFrom`) in `@manti-ui/tokens` instead of being written out stop by stop. - Neutral (`--manti-fill-*`) and variant (`--variant-fill-*`) alpha fill ladders give rest / hover / pressed washes a single place to tune. ## Components - **Select and Combobox** get a connected open-panel look; the panel border follows the active variant via `data-variant` on the portalled positioner. - **Combobox** renders removable chips in multi-select. - **Input** drops the focus-ring glow in favour of a border-colour change alone. --- URL: https://manti.design/components/ Summary: Browse all 53 accessible Manti UI React components - buttons, forms, overlays, navigation, and data display - each built on a Zag.js state machine. # Components Every component page includes live demos, props, public anatomy, and component tokens. Start with [Getting Started](/getting-started). For styling, use the [plain CSS](/guides/plain-css) or [Tailwind](/guides/tailwind) guide. --- URL: https://manti.design/typography/ Summary: The Manti UI type scale: font families, the xs-to-5xl size stops, weights, line heights, and the tracking that tightens as type grows. # Typography Manti UI uses a compact UI-first type scale. [Text](/typography/text) and [Heading](/typography/heading) put that scale behind a component API, with a neutral emphasis ladder and semantic color as two independent axes. The remaining primitives add Manti's token-backed visual language without giving up native HTML semantics: [Blockquote](/typography/blockquote), [Code](/typography/code), and [Kbd](/typography/kbd). ## Size `--manti-text-xs` through `--manti-text-5xl` provide interface and display sizes. Pair them with `--manti-leading-none`, `tight`, `snug`, `normal`, or `relaxed`. `none` (`1`) is for single-line chrome such as a day-cell number or a segmented-control label, where the box should hug the glyphs. ```css .card-title { font-size: var(--manti-text-xl); line-height: var(--manti-leading-snug); } ``` ## Weight Available weights are `regular` (400), `medium` (500), `semibold` (600), and `bold` (700). ## Tracking Optical letter-spacing. Large type reads as loose unless it is pulled in, and small label-ish text reads as cramped unless it is pushed out. The stops are `--manti-tracking-tighter` (`-0.02em`), `tight` (`-0.01em`), `normal`, `wide` (`0.02em`), `wider` (`0.04em`), and `widest` (`0.06em`). Because the values are in `em`, each stop scales with whatever font size it lands on. Text and Heading step through them automatically as `size` grows. ```css .hero-title { font-size: var(--manti-text-5xl); letter-spacing: var(--manti-tracking-tighter); } ``` ## Families - `--manti-font-sans` — Inter with system fallbacks - `--manti-font-mono` — system monospace stack Inter is not bundled. Load it in the application or override `--manti-font-sans`. --- URL: https://manti.design/changelog/v0.4.0/ Summary: TextField is renamed to Input and PasswordInput folds into , plus panel material roles are centralized. # v0.4.0 _Released 2026-07-19._ ## Breaking changes - **TextField → Input.** `TextField` is renamed to `Input`, and `PasswordInput` is folded into it as ``. The two always rendered the same `field` shell; the password affordances (show/hide toggle, Caps Lock warning) now belong to Input and apply to `type="password"` only — the same rule the inherited native attributes already follow (`min`/`max` for `type="number"`, `accept` for `type="file"`). ```diff -import { TextField, PasswordInput } from '@manti-ui/react'; +import { Input } from '@manti-ui/react'; - + - + ``` The password props are renamed to say which type they belong to, since names like `visible` and `showLabel` are ambiguous next to Input's own `label`: | `PasswordInput` | `Input` | | ---------------------- | ---------------------------- | | `showVisibilityToggle` | `showPasswordToggle` | | `visible` | `passwordVisible` | | `defaultVisible` | `defaultPasswordVisible` | | `onVisibilityChange` | `onPasswordVisibilityChange` | | `showLabel` | `showPasswordLabel` | | `hideLabel` | `hidePasswordLabel` | `showCapsLockWarning` is unchanged. `capsLockLabel` keeps its name but changes meaning: it is now an assistive-tech-only string (see below). - **CSS: the `password-input` scope is gone.** Its two parts moved onto the `field` scope that Input already renders. Any custom CSS targeting them must be retargeted: ```diff -[data-scope='password-input'][data-part='visibility-trigger'] +[data-scope='field'][data-part='visibility-trigger'] -[data-scope='password-input'][data-part='caps-lock'] +[data-scope='field'][data-part='caps-lock'] ``` The rest of the `field` anatomy, its component tokens (`--manti-field-*`), and Textarea are unaffected. ## Changed - **Input** — the Caps Lock warning is reduced to its icon so the amber "Caps Lock is on" text no longer competes with the value being typed. The wording moves into a visually-hidden `caps-lock-label` part — it still backs the `role="status"` live region and the input's `aria-describedby`, so it is hidden from the screen, not from the accessibility tree. `capsLockLabel` is therefore now an assistive-tech-only string. - **Panels** — repeated panel material values are centralized as semantic roles, making the treatment consistent and adjustable from one place. --- URL: https://manti.design/components/accordion/ Summary: Accordion stacks collapsible sections from an items array of value, title and content; set multiple for several open panels, defaultValue for initial state. # Accordion Pass `items` as `{ value, title, content }`. The Zag.js machine manages focus, keyboard navigation, and open panels. ## Multiple panels Set `multiple` to keep several panels open. Use `defaultValue` for the initial uncontrolled state. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/button/ Summary: Button is an action control with seven variants plus a link treatment, sizes, leading and trailing icon parts, a loading state, and an as prop for anchors. # Button Button supports the seven built-in variants, custom `MantiVariant` strings, and a Button-only `link` treatment. Set `as="a"` for navigation without nesting interactive elements. Native props and the ref type follow the selected root. Leading and trailing icons are decorative and render in public `leading-icon` / `trailing-icon` anatomy parts; the accessible name must come from children or `aria-label`. ## Sizes ## Loading `loading` blocks interaction, shows a Spinner, and preserves the button width. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/typography/text/ Summary: Text renders token-backed body copy with a three-step neutral emphasis ladder, semantic color variants, alignment, truncation, and line clamping. # Text Text renders body copy from the shared type scale. It carries no layout of its own beyond alignment and truncation, so it drops into a card, a form, or a table cell without fighting the surrounding grid. By default it renders a `

`. Use `as` when the element should be something else — a `` inside a sentence, a `

  • ` in a list. The styling stays identical; only the semantics change. ## Size and weight `size` selects a stop on the same scale the tokens expose, from `xs` through `5xl`. Line height and tracking tighten automatically as the size grows, because body leading on a display size reads as loose rather than generous. Weight rides the same prop, after a slash: ```tsx // stop only // stop and weight ``` The two halves are not equivalent. Writing the stop alone leaves the weight to the stylesheet, so a consumer who decides that `lg` means semibold in *their* system says it once in CSS instead of at every call site: ```css [data-scope='text'][data-part='root'][data-size='lg'] { --manti-text-weight: var(--manti-weight-semibold); } ``` Spelling out `lg/regular` overrides that again at the call site. ## Emphasis `emphasis` walks the neutral ladder. It answers "how loud is this line?", not "what color is it": all three rungs stay neutral and are contrast-checked in both themes. ## Variant `variant` is the separate, semantic color axis. Because the two axes are independent, a message can be red *and* quiet at the same time. When both are set, `variant` wins. ## Truncation `truncate` clamps to a single line with an ellipsis; `lineClamp` clamps to a given number of lines. Setting both leaves `lineClamp` in charge. ## API Text accepts every native attribute of whatever element `as` resolves to, in addition to the props below. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.3.0/ Summary: A framework-agnostic keyboard-shortcut primitive — useShortcut / useShortcuts — bindable to any component, plus a softer Button soft-variant highlight. # v0.3.0 _Released 2026-07-13._ ## Added - **useShortcut** — a new framework-agnostic keyboard-shortcut primitive. `useShortcut(combo, handler, options)` and `useShortcuts(map, options)` bind key combinations to actions on **any** component — Manti UI or your own. It supports modifier combos (`mod+k`, `ctrl+shift+p`, where `mod` resolves to ⌘ on macOS and Ctrl elsewhere) and key **sequences** (`g d`). Global by default (listens on `window`, so it fires regardless of focus with zero wiring) or scoped to a `ref`. Includes a form-element guard (won't fire while you type in an input/textarea/contenteditable unless `enableOnFormElements` is set), `preventDefault` on by default, plus `stopPropagation` and `enabled`. It is SSR-safe and backed by a pure-DOM engine in `@manti-ui/folds`, and is exposed standalone at `@manti-ui/react/shortcut` so it can be imported without pulling in any Manti UI component. ## Changed - **Button** — the `soft` variant's panel highlight is now a subtle inset glow rather than a top highlight line, for a softer raised feel. --- URL: https://manti.design/components/switch/ Summary: Switch is an on and off control using role=switch, with defaultChecked or checked plus onCheckedChange, two sizes, and children or inputProps for its label. # Switch Use `defaultChecked` for uncontrolled state or `checked` with `onCheckedChange` for controlled state. The actual input uses `role="switch"`. `children` renders a visible label; use `inputProps` for `aria-label` or `aria-labelledby` when the label lives elsewhere. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/typography/heading/ Summary: Heading keeps semantic rank and visual size independent, so an h2 can render at any stop on the Manti type scale without reaching for a div. # Heading Heading renders a real `h1`–`h6` element with Manti's title treatment: tighter leading than body copy, semibold weight, and optical tracking that pulls in as the type grows. ## Level `level` picks both the element and a sensible default size, so plain markup already reads correctly. ## Level and size are independent Document outline and visual hierarchy are different problems. `size` overrides the stop `level` implies, which means a section can stay an `h2` for screen readers while laying out small, and a hero can be an `h1` at display size — without anyone reaching for a styled `
    `. If you genuinely need the look without the rank, `as` overrides the element and leaves the styling in place. ## Weight Weight rides the `size` prop after a slash, the same way [Text](/typography/text) does. Leaving it off keeps the stylesheet's default, which is bold at `level={1}` and semibold everywhere else: ```tsx // bold, from the level // same stop, weight overridden ``` ## Emphasis and variant Heading shares [Text](/typography/text)'s two color axes: `emphasis` for the neutral ladder and `variant` for semantic color. ## API Heading accepts every native attribute of the rendered heading element in addition to the props below. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.2.0/ Summary: A month-grid Calendar, a ColorPicker copy area with eyedropper and format tabs, a compact Tabs size, new Button and Checkbox tokens, and two anatomy changes. # v0.2.0 _Released 2026-07-04._ ## Breaking changes - **ColorPicker** — the editable hex `channel-input` part has been removed. The panel now shows the selected color in a read-only Clipboard field (see Added below), so any custom CSS targeting `[data-scope='color-picker'] [data-part='channel-input']` no longer matches. - **TagsInput** — each tag now renders as a single `item-preview` span; the outer `data-part='item'` wrapper is no longer rendered. Custom CSS targeting `[data-scope='tags-input'] [data-part='item']` no longer matches — target `[data-part='item-preview']` instead. ## Added - **Calendar** — a new standalone **month-grid** component: a weekday header plus a six-row day grid that shows an entire month at once, distinct from the popover DatePicker. It reuses the Zag.js `date-picker` machine in inline mode for date math, month navigation, locale/week-start, and grid a11y. Supports `selectionMode` (`single` / `multiple` / `range`) with a `tone` highlight, `readOnly` for a display-only month, `fixedWeeks` for constant height, and a `renderDay(day)` slot for per-day content such as event badges. Ships the component tokens `--manti-calendar-day-min-height`, `--manti-calendar-day-padding`, and `--manti-calendar-radius`. - **ColorPicker** — a new copy area in the panel: small `soft` Tabs switch the format (**HEX / RGBA / HSLA**) over a full-width Clipboard field showing the selected value, with one-click copy, alongside an **eyedropper** button wired to the machine's `getEyeDropperTriggerProps` (Chromium-only; a no-op where the EyeDropper API is unsupported). - **Tabs** — a `size` prop (`'sm' | 'md'`, default `'md'`, emitted as `data-size`). `sm` tightens the gap, trigger padding, and font for compact, embedded usage such as panels and toolbars. - **Button** — a `--manti-button-cursor` component token (default `default`), so an app can opt every button into `cursor: pointer` (or any other cursor) with one variable instead of overriding the rule. - **Checkbox** — a `--manti-checkbox-indicator-size` component token controlling the check indicator's size within the control, with per-size defaults. ## Changed - **Switch** — the thumb squishes (shrinks in height) while the control is pressed, for a more tactile press, staying vertically centered in the track. - **Button** — the pressed state is a crisper `translateY(1px)` nudge, dropping the previous subpixel scale. - **TagsInput** — the control padding tightens so the tag chips sit closer to the field edge. --- URL: https://manti.design/components/card/ Summary: Card is a composable surface built from Card.Header, Card.Title, Card.Description, Card.Body and Card.Footer, with elevated and interactive variants. # Card Compose Card with `Card.Header`, `Card.Title`, `Card.Description`, `Card.Body`, and `Card.Footer`. ## Elevated and interactive `elevated` strengthens the surface tint. `interactive` adds hover and focus movement; pair it with real link or button semantics. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/typography/blockquote/ Summary: Blockquote renders a semantic block quotation with a quiet Manti accent rule, an optional citation, and type drawn entirely from the scale tokens. # Blockquote Blockquote renders the native `
    ` element for quotations from another source. Its leading accent, spacing, type, and color all resolve through Manti tokens. ## Attribution The native `cite` attribute stores a source URL but does not display it. Put visible attribution next to the Blockquote in your own `` element when readers need to see the source. ## API Blockquote accepts every native `
    ` attribute in addition to the props below. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.5/ Summary: Two fixes: Tabs no longer leak trigger and indicator styles into embedded components, and Clipboard keeps a neutral focus border until a copy succeeds. # v0.1.5 _Released 2026-07-03._ ## Fixed - **Tabs** — variant rules for the `trigger` and `indicator` parts matched any descendant under the root, so components rendered inside tab content (for example a Clipboard's copy/check icon wrappers, which are `indicator` parts) picked up the sliding-thumb chrome and rendered as a stray box. Both parts are now anchored through `> [data-part='list'] >`, so only the tabs' own anatomy is styled and embedded components are left alone. No visual change to the tabs. - **Clipboard** — `:focus-within` used the tone ring for its border, so with the default `tone="success"` simply clicking into the field turned it green, indistinguishable from the copied confirmation. Focus now uses the neutral `--manti-focus-ring`, and the tone tint applies only while `data-copied` is set (during the copied timeout). --- URL: https://manti.design/components/avatar/ Summary: Avatar renders a user image from src and falls back to children initials when it fails, with sm to xl sizes and a circle or square shape. # Avatar Avatar shows `src` when it loads and renders `children` as the fallback. Use `size` from `sm` to `xl` and `shape="circle"` or `"square"`. ## Fallback and shape ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/badge/ Summary: Badge is a compact label or status chip in primary, secondary, success, info, danger and outline variants, with sizes and an optional leading dot marker. # Badge Badge supports every built-in variant except `tertiary`: `primary`, `secondary`, `success`, `info`, `danger`, and `outline`. ## Dot Set `dot` to add a leading status marker. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/typography/code/ Summary: Code renders semantic inline code with a token-backed Manti treatment: monospace family, a subtle surface, and a size that tracks the copy around it. # Code Code renders the native inline `` element. Use it for identifiers, commands, package names, and short code fragments inside prose; use a dedicated code block for multiline source. ## API Code accepts every native `` attribute in addition to the props below. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.4/ Summary: A reworked Splitter resize handle that widens without reflowing panels, plus three Splitter component tokens. # v0.1.4 _Released 2026-07-01._ ## Changed - **Splitter** — the resize handle no longer reflows the panels when it grows. The trigger is now a fixed-width grab track holding a thin line that tones and widens via an outline on hover/drag, rather than animating the track's own width. The active state is keyed on hover/drag instead of focus, so the handle no longer stays stuck in the active tone after a mouse drag ends. ## Added - **Splitter** — three component tokens for the handle: `--manti-splitter-handle-size` (the grab-track thickness), `--manti-splitter-line-size` (the resting line), and `--manti-splitter-line-size-active` (the widened band on hover/drag). --- URL: https://manti.design/components/alert/ Summary: Alert is an inline status message in primary, secondary, success, info or danger variants, using role=status by default and role=alert for danger. # Alert Alert supports `primary`, `secondary`, `success`, `info`, and `danger`. It uses `role="status"` by default and `role="alert"` for danger. ## Dismissal Provide `onDismiss` to show a close button. Your application owns whether the Alert remains rendered. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/typography/kbd/ Summary: Kbd renders a semantic keyboard-input label styled as a compact key cap, for documenting shortcuts and the keys a component responds to. # Kbd Kbd renders the native `` element for keyboard input while applying Manti typography, surface, radius, spacing, and elevation tokens. The command symbol (`⌘`) automatically renders as `Ctrl` on non-Mac platforms. Use separate Kbd elements for each key in a combination so assistive technology and copied text preserve the shortcut structure. ## API Kbd accepts every native `` attribute in addition to the props below. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.3/ Summary: A centered ColorPicker slider thumb and an optional swatch-only trigger. # v0.1.3 _Released 2026-07-01._ ## Fixed - **ColorPicker** — the hue and alpha slider thumbs now sit centered on the track. They previously hung low because Zag positions the channel-slider thumb with `top: 50%` but, unlike the area thumb, adds no centering transform; the track also no longer clips the taller thumb. ## Added - **ColorPicker** — a `showValueText` prop (default `true`). Set it to `false` to show only the color swatch in the trigger, hiding the formatted value text (e.g. `rgba(124, 58, 237, 1)`). --- URL: https://manti.design/components/tabs/ Summary: Tabs renders items of value, label, content, icon and disabled in line, pill or soft variants, managing focus, arrow keys, selection and panels across sizes. # Tabs Pass items as `{ value, label, content, icon?, disabled? }`. The machine manages focus, arrow keys, selection, and panels. ## Variants Use `line`, `pill`, or `soft`. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.2/ Summary: A new Textarea multiline field, per-node TreeView icons, scrollable TimePicker columns, a tonal Listbox selection, and Combobox and TagsInput refinements. # v0.1.2 _Released 2026-06-29._ ## Added - **Textarea** — a new multiline text field. It renders the shared `field` shell, so label, hint/description, error/invalid state, sizes (`sm`/`md`/`lg`), and focus-ring `tone` all match `TextField`. Adds `autoResize` to grow with its content (bounded by `maxHeight`), `fullWidth`, and a `resize` affordance. - **TreeView** — an `icon` render prop for per-node leading icons. It receives the node and its live state, so the icon can react to expansion/selection (e.g. folder/file icons). ## Changed - **TimePicker** — its columns now scroll inside a Manti `ScrollArea`, and the panel is wider so the hour/minute/second columns stay comfortable. - **Listbox** — the selected item now gets a tonal background, making the current selection easier to spot. ## Fixed - **Combobox** — the selection now stays in sync with the typed text. - **TagsInput** — refined the spacing of the item preview. --- URL: https://manti.design/components/dialog/ Summary: Dialog is a modal panel opened from a trigger element, with title, description, a footer receiving close, sizes, focus trapping, dismissal and scroll locking. # Dialog Pass a React element as `trigger`, then provide `title`, `description`, and body content. The machine manages focus, dismissal, and scroll locking. ## Footer actions `footer` may be a function receiving `{ close }`. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.1/ Summary: A TanStack-backed DataTable, a categorized collapsible docs sidebar, the panel token rename, scrollable Combobox and Select listboxes, and overlay fixes. # v0.1.1 _Released 2026-06-26._ ## Breaking changes - **Frosted-panel tokens renamed** from `--manti-glass-*` to `--manti-panel-*` (e.g. `--manti-glass-tint` → `--manti-panel-tint`, `--manti-glass-border` → `--manti-panel-border`). Update any custom CSS that referenced the old names. ## Added - **DataTable** — a new data grid, and Manti's first component built on **TanStack** rather than Zag.js. The framework-agnostic `@tanstack/table-core` contract is re-exported through `@manti-ui/folds`, with the `@tanstack/react-table` runtime in the React adapter. It supports: - **Sorting** — click-to-sort headers (`sortable`) with `aria-sort` and a direction arrow. - **Filtering** — a global search box (`filterable`), composing `TextField`. - **Pagination** — client-side (`paginated` / `pageSize`), composing `Pagination`. - **Selection** — a leading checkbox column (`selectable` / `onSelectionChange`), composing `Checkbox`. - Density sizes, sticky header, zebra striping, row hover, per-column alignment via `meta.align`, and an optional toolbar `title`. - **Checkbox** gains an `aria-label` prop for controls without a visible label. - **Carousel** exposes a `slide-gap` component token (`--manti-carousel-slide-gap`) for the space between slides. ## Changed - **Combobox** and **Select** now scroll their listbox inside a Manti `ScrollArea`, so long option lists stay tidy. ## Fixed - **Combobox** and **Select** only mount their portal listbox while it is open. - **Overlay panels** — floating panels, popovers and other portaled surfaces are now opaque enough to keep the UI beneath them hidden and legible. - **FileUpload** squares its delete trigger and renders each file's size. ## Docs - The **component sidebar is now categorized** into Inputs, Buttons & Actions, Navigation, Overlays, Feedback, Data Display, and Layout — and each category is **collapsible**. - A **"New" badge** marks freshly added components in the sidebar. - A dedicated **Changelog** section (this page), newest release first. - The **NavigationMenu** demo flyout no longer gets clipped inside the playground. - Per-route **SEO**: titles, descriptions, canonical URLs, Open Graph / Twitter cards, a sitemap, and robots.txt. - Docs and **Storybook now ship from a single Netlify deploy** — Storybook lives at [`/storybook`](/storybook/). --- URL: https://manti.design/components/calendar/ Summary: Calendar is an inline month grid taking ISO date strings, with single or range selectionMode, a renderDay prop for per-day content, and a readOnly mode. # Calendar Calendar is an inline month surface. Use [DatePicker](/components/date-picker) when selection should open from a compact field. Values are ISO date strings. ## Selection Use `selectionMode="single"` or `"range"`. Controlled and uncontrolled values are arrays of ISO dates. ## Day content Use `renderDay(day)` for events or other day details. Pair it with `readOnly` for a display-only calendar. ## API ## Styling ### Anatomy Calendar's inner grid keeps `data-scope="date-picker"`. Scope grid overrides under `[data-scope="calendar"]` to avoid changing DatePicker. ### Tokens --- URL: https://manti.design/components/carousel/ Summary: Carousel renders a slides array with drag, keyboard navigation, snapping, arrows and page indicators, plus slidesPerPage for multi-item pages and loop. # Carousel Pass a `slides` array. The machine manages drag, keyboard navigation, snapping, arrows, and page indicators. ## Several slides per page Use `slidesPerPage` for multi-item pages and `loop` to wrap. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/tooltip/ Summary: Tooltip shows a short label on pointer hover or keyboard focus around a focusable trigger, with placement, delays, controlled open, and portalled rendering. # Tooltip Wrap a focusable trigger and pass `content`. The machine manages placement, delays, focus, hover, and dismissal. Set `interactive` only when the pointer must enter the tooltip content. Use `placement`, controlled `open` state, and `portalled` when the trigger lives inside overflow or stacking contexts. `portalContainer` targets a specific portal host. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/changelog/v0.1.0/ Summary: The first public release of Manti UI — the design-token contract, the monochrome design signature, and the initial component set, published to npm. # v0.1.0 _Released 2026-06-23._ The first public release of Manti UI — a framework-agnostic design system built on [Zag.js](https://zagjs.com) behavior machines. ## Highlights - **Framework-agnostic foundation.** Behavior lives in Zag.js machines; tokens and CSS are framework-free. A React renderer ships today, with Vue, Svelte and Solid able to reuse the same foundations. - **Three-tier design tokens.** Primitive ramps → semantic roles & tones → per-component tokens, themeable entirely from CSS variables (and bound to Tailwind v4). - **A full React component library**, with keyboard support, focus management and screen-reader semantics owned by the machines. - **This documentation site**, built end-to-end with Manti UI itself. ## Install ```bash npm install @manti-ui/react @manti-ui/styles ``` See [Getting Started](/getting-started) to render your first component, or browse the [Components](/components). --- URL: https://manti.design/components/checkbox/ Summary: Checkbox is a boolean control with checked, unchecked and indeterminate states, defaultChecked for uncontrolled use, onCheckedChange, and sm to lg sizes. # Checkbox Use `defaultChecked` for uncontrolled state or `checked` with `onCheckedChange` for controlled state. `indeterminate` shows a mixed value. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/ Summary: The foundations of Manti UI: the design token contract, color ramps and semantic roles, the type scale, motion levels, and the four-layer architecture. # Foundations Understand the visual language and system decisions behind Mantı UI before adapting them to your product. Looking for implementation steps? Continue with the [Guides](/guides/plain-css). --- URL: https://manti.design/components/clipboard/ Summary: Clipboard is a copy-to-clipboard field: pass the text as value and use timeout to control how long the copied confirmation state stays visible. # Clipboard Pass the text to copy as `value`. `timeout` controls how long the copied state remains visible. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/design-signature/ Summary: The design signature of Manti UI: sleek monochrome panels, no gradients and no box shadows, with color reserved for semantic variants alone. # Design signature Manti UI aims to feel **smooth**: quiet surfaces, clear state changes, and predictable interaction. ## Material The default interface is cool, near-neutral, and monochrome. Color appears through semantic variants instead of decoration. Floating surfaces share one panel treatment: - translucent surface tint; - subtle border; - blur when the browser and layout support it. The signature surface {/* An expression, not markdown: prose on its own lines inside a JSX element is parsed as a markdown block and wrapped in a

    , which would nest inside the

    that Card.Description renders. A browser un-nests that while parsing the prerendered HTML, breaking hydration for the route. */} {'A rounded panel with a quiet border and clear surface contrast.'}

    Use .manti-panel for application-owned menus, sheets, or command palettes. Manti falls back to an opaque surface when blur is unavailable.
    ## Main controls ```css .manti-app { --manti-panel-tint: oklch(0.2 0.008 280 / 0.55); --manti-panel-border: oklch(1 0 0 / 0.09); --manti-panel-blur: blur(16px) saturate(160%); } ``` For broader customization, continue to [Color & variants](/foundations/color-and-tones), [Design tokens](/foundations/tokens), and [Motion](/foundations/motion). --- URL: https://manti.design/components/collapsible/ Summary: Collapsible shows and hides a single region from a trigger, controlled with open and onOpenChange; indicator accepts false or a React node to replace it. # Collapsible Pass the button content as `trigger` and the revealed region as `children`. Use `open` with `onOpenChange` to control it. The default indicator follows state. Pass `indicator={false}` to hide it or a React node to replace it. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/color-and-tones/ Summary: Manti UI color: ordered 1-12 primitive ramps, theme-aware semantic roles, the rest/hover/active interaction ladder, and the seven semantic variants. # Color & variants Manti UI separates fixed color ramps from the semantic roles components use. ## Primitive ramps Six OKLCH ramps run from `1` (lightest) to `12` (darkest): | Ramp | Purpose | Use it for | | -------- | ----------- | ------------------------------------------------------ | | `gray` | Neutral UI | Surfaces, borders, text, and secondary controls | | `orange` | Primary | Main actions, selected states, and the product accent | | `green` | Success | Positive feedback, completed states, and confirmations | | `blue` | Information | Informational feedback, notices, and helpful context | | `amber` | Warning | Caution, attention, and states that need review | | `red` | Danger | Errors, destructive actions, and critical feedback | Each stop is available as `--manti-{ramp}-{step}`. Component CSS should use semantic tokens or variants instead of choosing a stop directly. ## Primitive scale contract This mapping comes directly from `colorScaleRoles` in `@manti-ui/tokens`. Every color ramp uses the same contract: | Stop | Source role | Exact purpose | | ---- | ---------------------------- | ------------------------------------------------- | | `1` | `appBackground[0]` | Lighter page or panel background position | | `2` | `appBackground[1]` | Next page or panel background position | | `3` | `componentBackground.rest` | Resting component background | | `4` | `componentBackground.hover` | Hovered component background | | `5` | `componentBackground.active` | Active, pressed, or selected component background | | `6` | `border.subtle` | Subtle borders and separators | | `7` | `border.interactive` | Interactive borders | | `8` | `border.strong` | Strong borders and focus chrome | | `9` | `solid.rest` | Resting solid fill | | `10` | `solid.hover` | Hovered solid fill | | `11` | `text.lowContrast` | Supporting or lower-contrast text | | `12` | `text.highContrast` | Primary or highest-contrast text | This is the palette's intended role contract, not a promise that every component reads the primitive stop literally. Components consume semantic tokens or `--variant-*` roles, and `tokens.css` adapts them for theme and contrast. The current `--variant-solid*` mappings are: | Variant | Rest | Hover | Active | | ------------------------------- | ------------------------ | ------------------------- | ------------------------- | | `primary` | `orange-7` | theme-aware `color-mix()` | theme-aware `color-mix()` | | `success`, `info`, and `danger` | matching hue at step `8` | theme-aware `color-mix()` | theme-aware `color-mix()` | | `secondary` | `gray-11` / `gray-3` | `gray-10` / `gray-4` | `gray-9` / `gray-5` | | `tertiary` and `outline` | semantic neutral value | semantic neutral value | semantic neutral value | Values separated by `/` mean light theme / dark theme. These mappings deliberately depart from the nominal `9 → 10` solid pair when contrast requires it. Input-like controls are the main exception to the colored-border progression. They use the neutral border at rest and hover, then introduce the variant color at focus or open. ## Built-in variants Manti ships seven variant palettes: - `primary` — orange, highest emphasis; - `secondary` — neutral soft surface; - `success` — green feedback; - `info` — blue feedback; - `tertiary` — neutral ghost; - `danger` — red feedback; - `outline` — neutral bordered treatment. Individual components may expose a subset. Alert supports `primary`, `secondary`, `success`, `info`, and `danger`. Badge omits `tertiary`. Button also adds `link`. Every variant defines the same roles: ```text --variant-solid --variant-solid-hover --variant-solid-active --variant-on-solid --variant-soft-bg --variant-soft-bg-hover --variant-soft-bg-active --variant-soft-text --variant-border --variant-text --variant-ring ``` ## Custom variant Components typed with `MantiVariant` accept custom strings: ```css [data-variant='brand'] { --variant-solid: var(--manti-blue-9); --variant-solid-hover: var(--manti-blue-10); --variant-solid-active: color-mix(in oklab, var(--manti-blue-10) 88%, black); --variant-on-solid: var(--manti-text-on-accent); --variant-soft-bg: var(--manti-blue-2); --variant-soft-bg-hover: var(--manti-blue-3); --variant-soft-bg-active: var(--manti-blue-4); --variant-soft-text: var(--manti-blue-9); --variant-border: var(--manti-blue-7); --variant-text: var(--manti-blue-9); --variant-ring: var(--manti-blue-8); } ``` ```tsx ``` ## Contributor checks Use semantic roles in component CSS, keep foregrounds explicit, and verify both themes: ```bash pnpm check:color-scale pnpm --filter @manti-ui/styles check:contrast pnpm --filter @manti-ui/styles build ``` --- URL: https://manti.design/components/color-picker/ Summary: ColorPicker accepts hex, rgba, hsla, hsba and oklch strings and opens an area, channel sliders, format tabs, an eyedropper and an editable copy field. # ColorPicker ColorPicker accepts `hex`, `rgba`, `hsla`, `hsba`, and `oklch` CSS color strings. Its panel includes a color-space area, channel sliders, format tabs, an eyedropper, and an editable copy field. `format` controls the output string; `colorSpace="oklch"` keeps palette interaction in native OKLCH channels, so changing the area or sliders does not round-trip through sRGB. `defaultFormat` sets the initial format when `format` is uncontrolled. Use `formats` to limit the copy tabs; the selected format is included automatically. ## Sizes `size` sets the trigger's height, padding, type, and swatch. The panel keeps its own canvas; its channel controls use the same size channel as the trigger. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/combobox/ Summary: Combobox is a typeahead select that filters an options array by label, supports single or multiple selection, a fill variant, and size-matched popup rows. # Combobox Pass options as `{ value, label, disabled? }`. Filtering runs by label while the Zag.js machine manages focus, keyboard navigation, and selection. ## Appearance Use `variant="fill"` for a neutral filled control. `size` uses the shared size scale: the portalled list follows the control, so a `sm` Combobox filters into `sm` rows. ## Multiple selection Set `multiple` to collect several values. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/tokens/ Summary: The three token tiers of Manti UI - primitive ramps, theme-aware semantic roles, and public --manti-{component}-{property} override tokens. # Design tokens Every visual value in Manti UI comes from the `@manti-ui/tokens` contract. ## Three tiers ### 1. Primitives Fixed color ramps and scale values for radius, control height, spacing, typography, elevation, and motion. These values are generated into `@manti-ui/styles`. Contributors edit `packages/tokens/src/index.ts`, then run `pnpm gen:tokens`. ### 2. Semantic roles and variants Purpose-based values such as: ```text --manti-bg --manti-surface --manti-border --manti-text --manti-text-muted --manti-focus-ring --variant-solid --variant-soft-bg --variant-ring ``` Use this tier for system-wide theming. ### 3. Component tokens Public per-component overrides named `--manti-{component}-{property}`: ```css :root { --manti-button-radius: var(--manti-radius-full); --manti-switch-track-width: 3rem; } ``` Use a component token only when that component should intentionally differ. Derived geometry stays private as `--_*`. ## Scales - Radius: `--manti-radius-xs` through `--manti-radius-2xl`, plus `full` - Control height: `--manti-control-height-sm | md | lg` - Spacing: `--manti-space-0` through `--manti-space-16` - Type: `--manti-text-xs` through `--manti-text-5xl` - Weight: `--manti-weight-regular | medium | semibold | bold` - Motion: `--manti-duration-*` and `--manti-ease-*` `--manti-radius-factor` rescales the radius ramp. Use `data-radius="none"`, `"sharp"`, `"default"`, `"round"`, or `"pill"` for presets. `none` is the square mode; the other four are positive shape profiles. ## Override scope Custom properties can be changed globally or on a subtree: ```css .marketing { --manti-surface: var(--manti-surface-raised); --manti-radius-factor: 1.4; } ``` See [Theming with tokens](/guides/theming-tokens) for the decision guide and [plain CSS](/guides/plain-css) for selector overrides. --- URL: https://manti.design/components/context-menu/ Summary: ContextMenu opens on right-click or long press over a wrapped region, taking Menu-style items with recursive submenu entries and a size-driven row rhythm. # ContextMenu Wrap a region in `children` and pass Menu-style `items`. ContextMenu uses the same Zag.js machine and styling scope as [Menu](/components/menu). Nested items use the same recursive `{ type: 'submenu', value, label, items }` shape as Menu. No separate nested context-menu component is required. ## Sizes `size` sets the panel's row rhythm, matching the scale every other control uses. Submenus inherit it. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/motion/ Summary: Motion in Manti UI: the data-motion levels, duration and easing tokens, and how every animation collapses under a reduced-motion preference. # Motion Set `data-motion` on any container: ```html
    ``` | Value | Result | | --------- | ----------------------------------- | | `default` | Standard component motion | | `none` | Decorative Manti motion is removed | | `full` | Expressive spring and bounce motion | Nested values override their ancestor. The Spinner keeps its functional rotation in `none`. ## Tokens Durations: ```text --manti-duration-fast --manti-duration-base --manti-duration-slow --manti-duration-slower ``` Easing: ```text --manti-ease-smooth --manti-ease-soft --manti-ease-spring --manti-ease-bounce ``` ```css .panel { transition: transform var(--manti-duration-base) var(--manti-ease-smooth), opacity var(--manti-duration-fast) var(--manti-ease-soft); } ``` `prefers-reduced-motion: reduce` overrides every tier, including `full`. --- URL: https://manti.design/components/date-picker/ Summary: DatePicker is a date field using ISO YYYY-MM-DD strings, with a popover calendar grid, month navigation, keyboard selection and a range selectionMode. # DatePicker DatePicker uses ISO strings (`YYYY-MM-DD`). The Zag.js machine manages the popover, calendar grid, month navigation, and keyboard selection. ## Sizes `size` sets the control's height, padding, and type, and the portalled calendar follows it down to the day cells. ## Range Set `selectionMode="range"` and provide two dates for a controlled or default value. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/foundations/architecture/ Summary: How Manti UI separates its four layers - design tokens, layered CSS, framework-agnostic Zag.js behavior, and the thin React renderer - and why. # Architecture Manti UI splits each concern into one package: | Package | Responsibility | | ------------------ | ------------------------------------------- | | `@manti-ui/tokens` | Typed design-token contract | | `@manti-ui/styles` | Layered CSS for public component anatomy | | `@manti-ui/folds` | Zag.js adapters and Manti-authored behavior | | `@manti-ui/react` | React components and hooks | Only the React renderer is available today. Tokens, CSS, and behavior remain framework-agnostic. ## Component flow ```text behavior machine ↓ React adapter ↓ data-scope / data-part / data-state ↓ shared CSS and tokens ``` Behavioral components connect a machine and render its prop getters: ```tsx const service = useMachine(component.machine, { id: useId(), ...props }); const api = component.connect(service, normalizeProps); return
    ; ``` Static components render the same anatomy without a machine. ## Public styling contract Target documented `data-scope`, `data-part`, variant, size, and state attributes. Class names and DOM between documented parts are private. ```html ``` `@manti-ui/react` has no CSS side effects. Import a stylesheet explicitly so the app controls cascade order or can run headlessly. --- URL: https://manti.design/components/drawer/ Summary: Drawer is a modal panel anchored to the left, right, top or bottom edge, reusing Dialog focus trapping, scroll locking and dismissal; size sets width or height. # Drawer Drawer reuses Dialog behavior for focus, scroll locking, and dismissal. Pass a React element as `trigger` and content as `children`. ## Placement and size `placement` is `left`, `right`, `top`, or `bottom`. `size` controls width for left/right and height for top/bottom. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/editable/ Summary: Editable is inline text that switches between preview and edit modes, with activationMode controlling how editing starts and submitMode how a value commits. # Editable `activationMode` controls how editing starts. `submitMode` controls how the value is committed. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/file-upload/ Summary: FileUpload is a dropzone with a browse button and removable file rows, validating selection through the accept, maxFiles and maxFileSize props. # FileUpload Drop files or use the browse button. Accepted files appear as removable rows. ## Restrictions Use `accept`, `maxFiles`, and `maxFileSize` to validate selection. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/floating-panel/ Summary: FloatingPanel is a window-style surface opened from a trigger that users can drag, resize, minimize, maximize and restore, with its own title and body. # FloatingPanel Pass a `trigger`, title, and body. The panel can be dragged, resized, minimized, maximized, and restored. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/guides/themes/ Summary: Adopt a shipped Manti UI theme with a single import, or scope several themes on one page, without touching a component's own styles. # Theme presets Manti ships six themes. Five of them are a stylesheet you import after `index.css`; the sixth is what you already have. ```js import '@manti-ui/styles/index.css'; import '@manti-ui/styles/themes/ocean.css'; ``` That is the whole adoption cost. Every component re-skins: the variant palettes, the neutral hue, the progress fill, and the text selection. ## What a preset moves A preset is a palette and nothing else. Each one expands its base colors into the full `--variant-*` vocabulary, re-points `--manti-accent-fill` and `--manti-selection-*` (the two branded roles no `[data-variant]` block can reach), and sets `--manti-cool-hue`, the single hue behind every gray, surface, border and text role. Shape, density and type are deliberately out. Those are product decisions, and a theme you adopt for its color must not quietly resize your controls or round your corners: swap Ocean for Forest and only the color changes. Retune the radius ramp with `--manti-radius-factor`, or the whole shape profile with `data-radius`, as its own decision. The definitions live in `presets` in `@manti-ui/tokens`, and `packages/styles/scripts/gen-preset-css.mjs` generates the stylesheets from them. The contrast floor and the interaction progression are checked per preset in the styles build, so a theme that fails either never ships. ## Override on top of one Theme files sit in `@layer manti.theme`, above the token defaults and below anything unlayered. Adopt a preset and retune a role on top of it: ```css @import '@manti-ui/styles/index.css'; @import '@manti-ui/styles/themes/forest.css'; :root { --manti-cool-hue: 280; } ``` Import order between the two stylesheets does not matter — the layer decides. ## More than one theme on a page For a gallery, a themed section, or a preview pane, import the scoped file once and set `data-manti-theme` on any container: ```js import '@manti-ui/styles/themes.css'; ``` ```html
    ``` Nested scopes re-resolve the neutral ramp and every surface, border and text role locally, so a scoped preset looks the same as its `:root` twin. `data-manti-theme` is independent of `data-theme` — light and dark still follow the OS or whatever you force. ## When a preset is not enough Reach for [theming with tokens](/guides/theming-tokens) for a targeted change, or the [Theme Studio](https://studio.manti.design) to tune every role on a live product surface and export the result. --- URL: https://manti.design/components/hover-card/ Summary: HoverCard opens a rich preview from a trigger element on pointer hover or keyboard focus, with the machine managing open delays, focus behavior and placement. # HoverCard Pass a React element as `trigger` and the preview as `children`. The machine manages delays, focus behavior, and placement. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/input/ Summary: Input is a labeled native input wiring helper text, error text, aria-describedby and aria-invalid, with left and right slots, sizes and password controls. # Input Input accepts native input attributes and wires label, helper text, error text, `aria-describedby`, and `aria-invalid`. ## Left and right content Use `left` and `right` to place text or any React node inside the input frame. Both slots share the control's focus, invalid, filled, and disabled states. This makes them suitable for units, currency symbols, icons, badges, shortcuts, or small actions. Spacing follows the kind of content. A text slot reads as part of the value and keeps the control's `padding-x`. Anything else is an element that paints its own box, so the control hands that side's padding to the slot, which insets it by `--manti-field-addon-padding-x` instead. Buttons, icons, and shortcut keys land near the frame edge without stacking two paddings. Give icon-only interactive content its own accessible label. `left` and `right` replace the deprecated `leadingAddon` and `trailingAddon` names; the old props remain available for backwards compatibility. ## Appearance and state Use `variant="fill"` for a neutral filled control. `size`, `error`, `disabled`, and native attributes work across both appearances. `requiredIndicator={null}` keeps native required semantics without adding a visible marker. When another product shell owns the visible focus treatment, set `focusRing="none"`. The forwarded ref always targets the native input. ## Passwords `type="password"` adds a reveal button and Caps Lock warning. Control them with the password-specific props; those props are ignored for other input types. ## API ## Styling ### Anatomy Input and [Textarea](/components/textarea) share the `field` scope. ### Tokens --- URL: https://manti.design/guides/plain-css/ Summary: Restyle Manti UI with ordinary CSS: override component tokens, target the stable data-scope and data-part anatomy, and never fork a component. # Customizing with plain CSS ## Import order Import Manti first and your overrides second: ```ts import '@manti-ui/styles/index.css'; import './theme.css'; ``` Manti CSS is layered. Ordinary unlayered CSS wins without `!important`. ## Prefer tokens Use semantic tokens for changes that should affect the system: ```css :root { --manti-bg: light-dark(#fafafb, #0c0c10); --manti-surface: light-dark(#ffffff, #15151b); --manti-text: light-dark(#16161a, #f4f4f6); --manti-border: light-dark(#e6e6ea, #2a2a33); --manti-radius-factor: 0.8; } ``` Use a component token for one intentional exception: ```css :root { --manti-button-radius: var(--manti-radius-full); } ``` ## Target anatomy When tokens are not enough, use public data attributes: ```css [data-scope='button'][data-part='root'] { letter-spacing: 0.02em; } [data-scope='button'][data-part='root'][data-variant='primary']:hover { translate: 0 -1px; } ``` Stable selectors include: - `data-scope` — component name; - `data-part` — anatomy part; - `data-variant` and `data-size`; - documented state attributes such as `data-state` or `data-loading`. Class names, private `--_*` variables, and undocumented DOM are not public API. ## Add a variant ```css [data-variant='brand'] { --variant-solid: var(--manti-blue-9); --variant-solid-hover: var(--manti-blue-10); --variant-solid-active: color-mix(in oklab, var(--manti-blue-10) 88%, black); --variant-on-solid: var(--manti-text-on-accent); --variant-soft-bg: var(--manti-blue-2); --variant-soft-bg-hover: var(--manti-blue-3); --variant-soft-bg-active: var(--manti-blue-4); --variant-soft-text: var(--manti-blue-9); --variant-border: var(--manti-blue-7); --variant-text: var(--manti-blue-9); --variant-ring: var(--manti-blue-8); } ``` ```tsx ``` Continue to [Theming with tokens](/guides/theming-tokens) for token-selection guidance or [Tailwind v4](/guides/tailwind) for utility integration. --- URL: https://manti.design/components/listbox/ Summary: Listbox is a selectable list of value and label options with roving focus, typeahead, single or multiple selectionMode, and sizes shared with Select and Menu. # Listbox Pass options as `{ value, label, disabled? }`. The machine manages roving focus, typeahead, and selection. ## Sizes `size` sets the row rhythm: type, padding, and icon size. It is the same scale Select, Combobox, and Menu use, so a list next to a `sm` control reads at the same size. ## Multiple selection Set `selectionMode="multiple"` and use array values. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/guides/theming-tokens/ Summary: Which Manti UI token to override: primitive ramps, semantic roles, the variant vocabulary, or a public per-component token - and when each applies. # Theming with tokens Use the broadest token that matches the design decision. Before writing any of this, check whether a [shipped preset](/guides/themes) already gets you there — adopting one is a single import. Reach for the tokens below to retune a preset, or to build a theme none of them cover. Prefer to see it before you write it? The [Theme Studio](https://studio.manti.design) tunes these same tokens live on a full product surface, then hands the result back as CSS, as an agent prompt, or as a shareable link. | Need | Use | | --------------------------------------- | --------------- | | Replace a palette or scale | Primitive token | | Change a system-wide purpose | Semantic token | | Change color and emphasis | Variant roles | | Make one component intentionally differ | Component token | ## Start with semantic roles ```css :root { --manti-bg: light-dark(#fafafb, #0c0c10); --manti-surface: light-dark(#ffffff, #15151b); --manti-surface-raised: light-dark(#ffffff, #1b1b22); --manti-text: light-dark(#16161a, #f4f4f6); --manti-text-muted: light-dark(#5c5c66, #b6b6c0); --manti-border: light-dark(#e6e6ea, #2a2a33); --manti-radius-factor: 0.8; --manti-duration-base: 180ms; } ``` Keep theme-aware colors in `light-dark(light, dark)` form. ## Use variants for color Remap a built-in variant on the whole app or within a subtree: ```css .marketing [data-variant='primary'] { --variant-solid: var(--manti-blue-9); --variant-solid-hover: var(--manti-blue-10); --variant-ring: var(--manti-blue-8); } ``` See [Color & variants](/foundations/color-and-tones) for the full role list. ## Use component tokens for exceptions ```css :root { --manti-button-radius: var(--manti-radius-full); } ``` If the same override appears on several components, move it to a semantic token instead. Do not override private `--_*` variables. ## Retune a size step `sm | md | lg` is one shared rhythm. A control and the surface it opens resolve the same `--manti-size-*` channel, so a `sm` Select opens a `sm` listbox and a `lg` Menu lays out `lg` rows. Popups are portalled, so each adapter re-stamps `data-size` on its positioner rather than relying on inheritance. Retune a step once and every control and popup on it follows: ```css [data-size='sm'] { --manti-size-item-padding-y: 0.125rem; --manti-size-text: 0.8125rem; } ``` The channel carries `text`, `text-sub`, `icon`, `control-height`, `control-padding-x`, `panel-padding`, `panel-gap`, `item-padding-y`, `item-padding-x`, `item-gap`, `item-radius`, and `cell`. Every component token above defaults to one of them, so a per-component override still wins. Continue to [plain CSS](/guides/plain-css) for anatomy selectors or [Tailwind](/guides/tailwind) for utility integration. --- URL: https://manti.design/components/marquee/ Summary: Marquee loops children in a seamless CSS animation running left, right, up or down, hides the duplicate from assistive tech, and stops under reduced motion. # Marquee Marquee duplicates `children` for a seamless CSS loop and hides the duplicate from assistive technology. It stops under reduced motion. ## Direction Use `left`, `right`, `up`, or `down`. Vertical marquees need a bounded height. `speed` is the duration of one pass. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/guides/tailwind/ Summary: Use Manti UI with Tailwind CSS v4: import Manti first so utilities win, then bridge the --manti-* tokens into Tailwind's theme variables. # Using with Tailwind v4 ## Styled components Import Manti before Tailwind: ```css @import '@manti-ui/styles/tailwind.css'; @import 'tailwindcss'; ``` The order keeps Tailwind utilities above Manti component styles: ```tsx ``` If Tailwind is imported first, utility overrides may lose to the Manti layer. ## Theme bridge The bridge maps Manti tokens to Tailwind v4 theme namespaces: | Utility | Uses | | ------------------------------- | -------------------------- | | `bg-surface`, `text-text-muted` | Semantic roles | | `bg-orange-500`, `text-red-700` | Primitive ramps | | `bg-primary-600` | Semantic ramp aliases | | `text-lg`, `font-semibold` | Typography | | `rounded-lg` | Radius | | `p-4`, `gap-2`, `h-control-md` | Spacing and control height | | `ease-spring` | Motion | Utilities resolve to `var(--manti-*)` at runtime, so token overrides and `data-theme` changes continue to work. ```css :root { --manti-radius-factor: 0.8; --manti-surface: light-dark(#ffffff, #15151b); } ``` ## Headless use Skip component CSS and keep only tokens plus the theme bridge: ```css @import '@manti-ui/styles/tokens.css'; @import '@manti-ui/styles/tailwind-theme.css'; @import 'tailwindcss'; ``` Then style public state attributes directly: ```tsx ``` Tailwind v3 can reference `var(--manti-*)` from its config, but the `@theme` bridge is v4-only. --- URL: https://manti.design/components/menu/ Summary: Menu is a floating command list with actions, checkbox and radio items, groups, separators, shortcut hints and nested submenus, driven by items and onSelect. # Menu Menu displays a floating list of actions or choices. Pass the trigger element, describe the rows with `items`, and handle a selected action with `onSelect`. Manti manages positioning, focus, keyboard navigation, and dismissal. Use [Popover](/components/popover) instead when the floating panel contains form controls or arbitrary interactive content. ## Basic usage Start with an array of commands. Each command needs a unique `value` and a visible `label`. `onSelect` receives the selected value. Add `shortcut` to display a keyboard hint. It only displays the hint; register the actual shortcut separately with `useShortcut`. ## Sizes `size` sets the panel's row rhythm: type, padding, and icon size. It matches the scale every other control uses, so a `sm` trigger can open a `sm` menu. Submenus inherit the size of the menu that opened them. ## Groups and item states Use a group to label related actions and a separator to divide different sets. An item can also be disabled or use `tone: 'danger'` for a destructive action. Useful item properties: - `icon`: leading visual affordance; - `shortcut`: trailing keyboard hint; - `disabled`: visible but unavailable action; - `tone: 'danger'`: destructive action styling; - `onSelect`: handler for only that item. ## Checkable items Checkbox items represent independent settings. Radio items represent one choice from a set. Both are controlled: pass `checked` and update application state in `onCheckedChange`. Set `closeOnSelect: false` when a choice should leave the menu open. Radio exclusivity comes from application state rather than the group itself. ## Submenus A submenu is another `items` array nested inside an item. No separate submenu component is required. Submenus may contain more submenus. Keep every `value` unique across the whole tree. Selecting a leaf closes the complete tree; placement and keyboard routing are handled automatically. If the submenu label is not plain text, provide `ariaLabel`. ## Item reference `MenuItem[]` accepts these shapes: | Shape | Use for | | ------------------------------------------ | ------------------------- | | `{ value, label }` | Action | | `{ type: 'checkbox', … }` | Independent choice | | `{ type: 'radio', … }` | One choice from a set | | `{ type: 'group', label, items }` | Labelled group | | `{ type: 'separator' }` | Divider | | `{ type: 'submenu', value, label, items }` | Another level of commands | ## State and events The root `onSelect` observes every selectable item, including leaves inside a submenu. An item's own `onSelect` runs after the root handler. The root menu is uncontrolled by default. Use `open` with `onOpenChange` only when application state must own whether it is open: ```tsx Account} items={items} /> ``` `itemProps` adds DOM props to one item. `getItemProps` applies props to every action and submenu trigger. Prefer semantic properties such as `disabled` and `tone` before reaching for these lower-level escape hatches. ## Menu or Popover? Use Menu when every row is an action, checkbox, radio, or submenu trigger. Use Popover for ToggleGroup, Input, Select, Slider, forms, explanatory content, or a mixture of controls and commands. ## API ## Styling ### Anatomy ### Tokens --- # Working with AI URL: https://manti.design/working-with-ai/ Summary: Give coding agents what they need to write correct Manti UI code: the llms.txt project guide, a ready prompt, and the rules agents get wrong. Manti UI publishes a machine-readable project guide at [manti.design/llms.txt](https://manti.design/llms.txt). It covers installation, public APIs, components, tokens, styling rules, accessibility, and verification. When an agent needs the prose rather than the map, the whole documentation corpus is served as one plain-text file at [manti.design/llms-full.txt](https://manti.design/llms-full.txt), so it can be read in a single fetch instead of by crawling 93 pages. Give the following prompt to a coding agent. Replace the final task placeholder with what you want it to build. ## Copy this prompt ```text Build the following React interface with Manti UI. Before writing code, read https://manti.design/llms.txt and treat it as the source of truth for Manti UI installation, components, public APIs, styling, accessibility, and verification. Follow the relevant component documentation linked from that file and verify real exported props and types instead of inventing APIs. After implementation, run the project's available validation commands and summarize the changes and assumptions. Task: [Describe the interface or change you want the agent to implement.] ``` ## Keep the reference available Agents work best when they can access the URL directly. If network access is blocked, download [llms.txt](https://manti.design/llms.txt) and add it to the agent's context with your task. --- URL: https://manti.design/components/navigation-menu/ Summary: NavigationMenu builds site navigation from items of value, label and links, opening keyboard-accessible dropdowns of href, label and description entries. # NavigationMenu Pass top-level items as `{ value, label, links }`. Links use `{ href, label, description? }`. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/number-input/ Summary: NumberInput is a numeric field with keyboard and button stepping, constrained by min, max and step; onValueChange reports the string and parsed number. # NumberInput Use `min`, `max`, and `step` to constrain the value. `onValueChange` reports the string value and parsed number. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/guides/integration/ Summary: Integrate Manti UI with an existing codebase: global resets, focus shells, dense layouts, native form submission, and testing against the anatomy. # Application integration ## Cascade layers and application resets Manti styles live in cascade layers. Unlayered application CSS wins regardless of selector specificity, so a broad reset such as `button { padding: 0 }` can erase the geometry of Button, ToggleGroup, Menu, and other button-backed anatomies. Import Manti before application CSS and keep native resets away from Manti parts: ```css button:not([data-scope][data-part]) { padding: 0; } ``` When overriding anatomy, prefer low-specificity selectors: ```css :where([data-scope='button'][data-part='root']).compact-action { --manti-button-height: var(--app-compact-control-height); --manti-button-padding-x: var(--app-compact-control-padding); } ``` Component variables define control geometry. Setting only `height` does not override a component's token-backed `min-height`. ## Focus ownership Input and Textarea put the visible focus treatment on the outer `control` part; the native input is intentionally outline-free. If an existing search shell or composite widget already paints focus, use `focusRing="none"` and keep one visible owner: ```tsx ``` Do not remove both focus indicators. Global `input:focus-visible` rules should either exclude Manti field inputs or be placed in a controlled layer. ## Inline SVG Manti's reset makes SVG replaced media block-level. Wrap application icons or restore inline flow for text-adjacent SVG: ```css .app-inline-icon { display: inline-block; vertical-align: middle; } ``` Button's `leadingIcon` and `trailingIcon` slots already render inside inline-flex anatomy parts. ## Vitest and jsdom Zag/Floating UI components expect `PointerEvent` and `ResizeObserver`. Add small test-environment shims when jsdom does not provide them: ```ts class ResizeObserverMock implements ResizeObserver { observe() {} unobserve() {} disconnect() {} } globalThis.ResizeObserver ??= ResizeObserverMock; globalThis.PointerEvent ??= MouseEvent as typeof PointerEvent; ``` Test pointer, keyboard, and direct click activation independently for menus and other composite controls. ## Native forms Manti fields compose with the platform form element; a state-owning Form primitive is not required: ```tsx
    ``` Use `requiredIndicator={null}` when native required semantics should remain without a visual marker. ## Combined local date and time For a single native local datetime value, Input remains the smallest contract: ```tsx ``` When product requirements need separate DatePicker and TimePicker surfaces, place both under one visible field label and combine their values in application state. Keep one accessible group name and one validation message. --- URL: https://manti.design/components/pagination/ Summary: Pagination renders page buttons with ellipses from a total count and pageSize, with siblingCount for nearby pages, sizes, and controlled or uncontrolled state. # Pagination `count` is the total item count and `pageSize` is the number per page. `siblingCount` controls nearby page buttons. ## Many pages ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/pin-input/ Summary: PinInput is a segmented code field set by length and type, handling focus movement, paste, completion and form behavior, with a mask option and sizes. # PinInput Set `length` and `type`. The machine manages focus, paste, completion, and form behavior. ## Masking Set `mask` for secret values. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/popover/ Summary: Popover is a floating panel anchored to a trigger element, with a placement prop that flips when space runs out and an optional internal close button. # Popover Pass a React element as `trigger` and the panel body as `children`. The machine manages placement, focus, and dismissal. ## Placement `placement` anchors the panel to a side of the trigger, optionally aligned to its start or end edge. It defaults to `bottom`, and the machine flips the panel when there is not enough room on the requested side. ## Close button Set `showCloseButton` to add an internal dismiss control. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/progress/ Summary: Progress shows determinate or indeterminate completion as a linear bar or a circular ring, with sizes; omit value for the indeterminate state. # Progress The default `linear` view fills a horizontal track. Omit `value` for indeterminate progress. ## Circular Use `variant="circular"` for a ring. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/reference/zag-coverage/ Summary: Which Zag.js machines Manti UI has adapted, which are still to come, and which were built and then shelved, with the reason for each. # Zag.js coverage Manti UI adapts Zag.js machines through `@manti-ui/folds`. React components connect those machines and render the public anatomy; CSS owns appearance. ```tsx const service = useMachine(component.machine, { id: useId(), ...props }); const api = component.connect(service, normalizeProps); ``` ## Status ✅ shipped · ⬜ not implemented · 📦 built, then shelved | Area | Components | | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | | Inline behavior | ✅ Toggle, Switch, Checkbox, RadioGroup, Collapsible, Accordion, Tabs, Tooltip | | Overlays | ✅ Dialog, Popover, HoverCard, Menu, Toast | | Form inputs | ✅ NumberInput, PinInput, Slider, TagsInput, Editable, ToggleGroup | | Selection | ✅ Select, Combobox, Listbox, TreeView, Pagination | | Display and media | ✅ Avatar, Progress, RatingGroup, Carousel, Clipboard, FileUpload, SignaturePad · 📦 QrCode | | Date and advanced | ✅ DatePicker, TimePicker, Steps, Tour, Splitter, ColorPicker, NavigationMenu, FloatingPanel · 📦 Timer · ⬜ AngleSlider | ## Reused machines Some components reuse an existing adapter: | Component | Machine | | ---------------- | ---------- | | Calendar | DatePicker | | ContextMenu | Menu | | Drawer | Dialog | | SegmentedControl | RadioGroup | Manti also authors framework-agnostic behavior for Shortcut, Swipe, and DataTable. Marquee and ScrollArea are Manti components without Zag machines. Shelved source remains under `backlog/` and ships in no package. See the [component catalog](/components) for currently available React components. --- URL: https://manti.design/components/radio-group/ Summary: RadioGroup is a single-choice group of value and label options with arrow keys, roving focus and form behavior, in a vertical or horizontal layout. # RadioGroup Pass options as `{ value, label, disabled? }`. The machine manages arrow keys, roving focus, selection, and form behavior. ## Horizontal layout ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/rating-group/ Summary: RatingGroup is a star rating set by count, with keyboard input, hover preview, optional half values, form behavior, and size and variant options. # RatingGroup Set the star `count`, value, size, and variant. The machine manages keyboard input, hover preview, and form behavior. ## Half values ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/select/ Summary: Select is a listbox control taking value and label options with string array values for single or multiple selection, a fill variant, and size-matched rows. # Select Pass options as `{ value, label, disabled? }`. Values are string arrays for both single and multiple selection. ## Appearance Use `variant="fill"` for a neutral filled trigger. `size` uses the shared size scale: the portalled listbox follows the trigger, so a `sm` Select opens `sm` rows. ## Multiple selection ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/signature-pad/ Summary: SignaturePad is a pointer and touch drawing surface that records paths as controlled or uncontrolled state, with a clear button that resets the canvas. # SignaturePad The machine records drawn paths. Use controlled or uncontrolled `paths`; the clear button resets the surface. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/slider/ Summary: Slider is a value selector driven by min, max and step, with pointer dragging, keyboard steps, form state, two values for a range, and labeled marks. # Slider Set `min`, `max`, and `step`. The machine manages pointer dragging, keyboard steps, and form state. ## Range and marks Pass two values for a range. Add `marks` for labeled positions. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/spinner/ Summary: Spinner is an indeterminate loading indicator that inherits currentColor in sm, md and lg sizes; set label when no visible loading text accompanies it. # Spinner Spinner inherits `currentColor` and exposes `sm`, `md`, and `lg` sizes. Set `label` when it is not accompanied by visible loading text. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/splitter/ Summary: Splitter creates resizable panes from a panels array with default percentages, handling resize math, keyboard controls and panel constraints for two or more. # Splitter Pass a `panels` array and matching default percentages. The machine manages resize math, keyboard controls, and panel constraints. ## Several panels ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/steps/ Summary: Steps is a multi-step flow from items of title, description and content, tracking the active step with built-in controls, in vertical or horizontal layout. # Steps Pass items as `{ title, description, content }`. The machine tracks the active step and built-in controls move through the flow. ## Vertical layout ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/scroll-area/ Summary: ScrollArea is a bounded viewport with draggable theme-aware scrollbars, a type prop for auto, hover or always visibility, and vertical, horizontal or both axes. # ScrollArea Give the root a bounded height or width. `type` controls scrollbar visibility: `auto`, `hover`, or `always`. ## Orientation Use `vertical`, `horizontal`, or `both`. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/tags-input/ Summary: TagsInput is multi-value text entry where Enter adds a tag and Backspace removes the last, with editable and removable tags plus max, invalid and readOnly. # TagsInput Press Enter to add a tag and Backspace to remove the last one. The machine manages editing, navigation, and hidden form values. ## State and limits Use `max`, `invalid`, `disabled`, `readOnly`, and `required` as needed. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/textarea/ Summary: Textarea is a labeled native textarea wiring helper text, error text and ARIA relationships, with a fill variant, autoResize, maxHeight and focusRing controls. # Textarea Textarea accepts native attributes and wires label, helper text, error text, and ARIA relationships. ## Appearance and state Use `variant="fill"` for a neutral filled control. Set `autoResize` to grow with content and `maxHeight` to cap it. `requiredIndicator={null}` hides only the visual marker. Use `focusRing="none"` when an outer product shell owns focus styling. ## API ## Styling ### Anatomy Textarea and [Input](/components/input) share the `field` scope. ### Tokens --- URL: https://manti.design/components/time-picker/ Summary: TimePicker is a time field opening a floating panel of hour, minute and period columns, with parsing, keyboard navigation, and a size the panel inherits. # TimePicker The machine manages parsing, the floating panel, and keyboard navigation across time columns. ## Sizes `size` sets the control's height, padding, and type, and the portalled panel follows it down to the hour and minute cells. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/toast/ Summary: Toast sends transient notifications imperatively: create a toaster and Toaster host once with createToaster, then call semantic types with swipe dismissal. # Toast Create a store and host once, then send notifications from anywhere: ```tsx const { toaster, Toaster } = createToaster(); function App() { return ; } toaster.success({ title: 'Saved' }); ``` ## API The table describes `createToaster(options)`. ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/toggle/ Summary: Toggle is a pressable two-state button using defaultPressed for uncontrolled state or pressed with onPressedChange when application state owns the value. # Toggle Use `defaultPressed` for uncontrolled state or `pressed` with `onPressedChange` for controlled state. ## Controlled state ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/toggle-group/ Summary: ToggleGroup is a set of pressed buttons with roving focus and arrow keys, typed by type=single for a string value or type=multiple for a string array. # ToggleGroup Pass items as `{ value, label, disabled? }`. The machine manages roving focus, arrow keys, and pressed values. Use the discriminated API for type-safe values: `type="single"` uses a string, while `type="multiple"` uses a string array. The former array-only API remains available for compatibility. Give a single-select group an accessible name through `rootProps`. ## Multiple selection ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/segmented-control/ Summary: SegmentedControl is a single-select group of value items with a moving active indicator, built on the RadioGroup machine for keyboard and form behavior. # SegmentedControl Pass items as `{ value, label, disabled? }`. The RadioGroup machine provides keyboard and form behavior while the indicator follows the selected item. ## Sizes ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/tour/ Summary: Tour runs a guided product walkthrough from an ordered steps array and a trigger, where each step spotlights an element by CSS selector or appears centered. # Tour Pass an ordered `steps` array and a `trigger`. A step can target an element with a CSS selector or appear in the center. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/utilities/use-shortcut/ Summary: useShortcut binds keyboard combinations and multi-key sequences, either globally or scoped to a region, with the platform modifier resolved for you. # useShortcut `useShortcut` works with Manti components or any other element. ## Import ```tsx import { useShortcut, useShortcuts } from '@manti-ui/react/shortcut'; ``` The hooks are also exported from `@manti-ui/react`. ## One shortcut ```tsx useShortcut('mod+k', () => setOpen(true)); ``` Pass a ref as `scope` to fire only while focus is inside a region: ## Several shortcuts ```tsx useShortcuts({ 'mod+k': openSearch, 'mod+/': toggleHelp, 'g d': goToDashboard, }); ``` ## Form fields Shortcuts are ignored in `input`, `textarea`, `select`, and `contenteditable` elements by default. Set `enableOnFormElements` to opt in. ## Syntax | Form | Example | | -------- | ----------------------- | | Modifier | `mod+k`, `ctrl+shift+p` | | Key | `escape`, `enter`, `/` | | Sequence | `g d` | `mod` means Command on macOS and Control elsewhere. ## API ```ts useShortcut(combo, handler, options?): void useShortcuts(map, options?): void ``` | Option | Default | Purpose | | ---------------------- | ---------- | ---------------------------------- | | `scope` | `'global'` | Window or a ref-scoped subtree | | `preventDefault` | `true` | Stop the browser's matching action | | `enabled` | `true` | Toggle the binding | | `enableOnFormElements` | `false` | Allow shortcuts while typing | | `stopPropagation` | `false` | Stop event propagation | | `sequenceTimeout` | `1000` | Sequence timeout in milliseconds | The implementation is SSR-safe and attaches listeners only in an effect. --- URL: https://manti.design/components/tree-view/ Summary: TreeView renders nested items where branches carry children, managing expansion, selection and arrow keys, with an icon render prop for state-aware icons. # TreeView Pass nested items; branches contain `children` and leaves do not. The machine manages expansion, selection, and arrow keys. Use the `icon` render prop to display node and state-aware icons. ## API ## Styling ### Anatomy ### Tokens --- URL: https://manti.design/components/data-table/ Summary: DataTable is a typed grid built on TanStack table-core, with sortable columns, filtering, pagination, row selection, getRowProps hooks and sizes. # DataTable Pass `columns` and `data`. DataTable uses TanStack table-core for row models and renders semantic table markup. ## Columns and sorting Columns use the re-exported `DataTableColumn` type. Use `accessorKey` for plain values, `cell` for custom rendering, and `meta.align` for alignment. Set `sortable` to add accessible sort buttons. ## Entity stores and row props Use `getRowProps`, `getCellProps`, and `getRowId` for native, ARIA, and data attributes. When each row must resolve a live entity reference with hooks, pass `rowComponent`. It is a real React component boundary, unlike a render callback, so hooks remain valid. Spread `rowProps` onto its semantic `` and render `children` for the generated cells. ## Filtering, pagination, and selection Enable `filterable`, `paginated`, or `selectable` independently or together. ## Sizes ## API ## Styling ### Anatomy ### Tokens