Manicat UI

Frameworks

One library, several formats. React is the source of truth; React Native, Vue, Svelte and Swift ports sit beside it and install the same way.

Manicat UI is written in React, and every component can have a port for another framework. A port is the same component, in the idiom of the target: React Native styles instead of CSS, a Vue single file component instead of JSX. Ports are separate registry items, so installing one never pulls React code into a Vue project.

Formats

The registry of formats lives in packages/manicat/frameworks.json. A file belongs to a framework by its suffix.

FrameworkFileCustomise parts withInstalls
Reactbutton.tsxclassNames@manicat/button
React Nativebutton.rn.tsxstyles@manicat/button-react-native
Vuebutton.vueclassNames@manicat/button-vue
Sveltebutton.svelteclassNames@manicat/button-svelte
Swiftbutton.swiftstyles@manicat/button-swift

Only the ports that exist for an item are offered. The framework switcher on every component page lists the formats found for that item, and the choice is remembered in your browser: pick Vue once and every page opens on Vue when it has it.

A link can carry the choice too: /components/button?framework=vue opens on Vue for anyone, and the server renders the Vue tab and code, so curl sees them. #vue works in a browser as well, but a # fragment is never sent to a server, so use ?framework= when something other than a browser reads the page. Picking a tab updates the address to match.

Coverage

This table is built from the files on disk, so it is always current.

ComponentReactReact NativeVue
Button✓✓✓
Action button✓✓✓
Split button✓✓✓
Button group✓✓✓
Floating button group✓✓✓
Expanding button group✓✓✓
Dropdown menu✓✓✓
Context menu✓✓✓
Copy button✓✓✓
Drawer✓✓✓
Theme switcher✓✓✓
Eclipse✓✓✓
Split✓✓✓
Rise✓✓✓
Avatar✓✓✓
Avatar group✓✓✓
Input✓✓✓
Textarea✓✓✓
Select✓✓✓
Combobox✓✓✓
Checkbox✓✓✓
Switch✓✓✓
Multi-select✓✓✓
Number field✓✓✓
Password field✓✓✓
Search field✓✓✓
Tag input✓✓✓
File dropzone✓✓✓
Radio group✓✓✓
Segmented control✓✓✓
Calendar✓✓✓
Date picker✓✓✓
Time picker✓✓✓
Accordion✓✓✓
Dialog✓✓✓
Popover✓✓✓
Tooltip✓✓✓
Tabs✓✓✓
Expandable card✓✓✓
Breadcrumb✓✓✓
Alert✓✓✓
Toast✓✓✓
Progress✓✓✓
Skeleton✓✓✓
Badge✓✓✓
Card✓✓✓
Metric card✓✓✓
Empty state✓✓✓
Tree view✓✓✓
Pagination✓✓✓
Filter toolbar✓✓✓
Sortable data table✓✓✓
Sparkline✓✓✓
Gauge✓✓✓
Animated counter✓✓✓
Code block✓✓✓
Text reveal✓✓✓
In-view title✓✓✓
Text morph✓✓✓
Text shimmer✓✓✓
Hold to confirm✓✓✓
Swipe actions✓✓✓
Slider✓✓✓
Inline edit✓✓✓
Expanding search✓✓✓
Chip group✓✓✓
Password strength✓✓✓
Bottom sheet✓✓✓
Hover card✓✓✓
Resizable panels✓✓✓
Toast stack✓✓✓
Usage meter✓✓✓
Image compare✓✓✓
Carousel✓✓✓
Card stack✓✓✓
Bar chart✓✓✓
Activity heatmap✓✓✓
Timeline✓✓✓
User menu✓✓✓
Stepper✓✓✓
Signature pad✓✓✓
Date range picker✓✓✓
Color picker✓✓✓
Morph select✓✓✓
Line chart✓✓✓
Donut chart✓✓✓
Streamgraph✓✓✓
Brush chart✓✓✓
Ridgeline✓✓✓
Treemap✓✓✓
Waffle chart✓✓✓
Slope chart✓✓✓
Announcement bar✓✓✓
JSON viewer✓✓✓
Phone input✓✓✓
Money input✓✓✓
Shortcut recorder✓✓✓
Confirm morph✓✓✓
Mention input✓✓✓
Chat thread✓✓✓
Rich text editor✓✓✓
Billing toggle✓✓✓
Scroll area✓✓✓
Radio cards✓✓✓
Comment thread✓✓✓
Slot text✓✓✓

106 of 106 components have a port.

Installing a port

A port installs exactly like any other item, under its own name:

# React
npx shadcn@latest add @manicat/button

# React Native
npx shadcn@latest add @manicat/button-react-native

# Vue
npx shadcn@latest add @manicat/button-vue

# Svelte
npx shadcn@latest add @manicat/button-svelte

# Swift (SwiftUI)
npx shadcn@latest add @manicat/button-swift

React Native ports also install lib/rn-tokens.ts, the design tokens as plain values. Vue ports read the CSS variables of foundation.css, so they declare manicat-foundation as a dependency and the CLI adds it for you. Svelte ports do the same. Swift ports install lib/ManicatTokens.swift (tokens, metrics and the ManicatStyle helper), plus the sibling ports and helpers they use, such as ManicatChart.swift or ManicatPhone.swift.

What stays the same

  • Names. Props, variants and sizes keep their React names: variant="danger", size="sm", loading.
  • Tokens. Colors, radii, control heights and spacing come from the same token set, in both themes.
  • Part names. The parts you can style (track, thumb, label…) are the same in every format.

What differs

  • Compound components. React's Tabs, TabsList and TabsTrigger are Radix parts. The ports take the same information as data instead: items (plus a slot per tab in Vue, a content field in React Native). Dialog takes a trigger (a #trigger slot in Vue) and open/onOpenChange (v-model:open).
  • Floating layers. On the web the select, combobox and menu open as a popover under their trigger. On React Native they open as a sheet (Modal) or inline list, which is where a thumb reaches, and the tooltip shows on a long press because a phone has no hover.
  • Motion. Ports start with CSS transitions (Vue, Svelte), Animated (React Native) or SwiftUI animations. The spring-based morphs of the React version are not ported yet.
  • Focus and pointers. Web ports keep the quiet focus treatment; React Native follows the platform and uses accessibilityRole, accessibilityState and press feedback instead.
  • Previews. The live previews on this site render the React version. Choose another framework to read its install command, source, API table and usage snippet, or run its viewer (below) to see it running.

Viewers

Each port format has a small app that loads every port next to its React source and shows it beside the React demo. The demos are the Usage snippets of the docs, so what you copy is what you see.

FrameworkCommandPort
Sveltepnpm --filter @manicat/preview-svelte dev3001, at /preview/svelte/
Vuepnpm --filter @manicat/preview-vue dev3002, at /preview/vue/
React Nativepnpm --filter @manicat/preview-rn dev3003, at /preview/react-native/ (react-native-web, in a phone frame)
Swiftcd apps/preview-swift && swift runa native app, on a Mac

apps/viewer-shared/smoke.mjs <url> opens every demo of a running viewer in a headless browser and fails on a runtime error. The Swift viewer is a SwiftUI app whose demo list is generated from the same snippets.

On a component page, pick Vue, Svelte or React Native in the Preview switcher to see that port running. The page frames its viewer from its own address, /preview/vue, /preview/svelte or /preview/react-native (?embed shows only the demo), through a rewrite in next.config.ts. Visitors therefore only need the docs site to be reachable, never the viewers' ports. pnpm dev starts all three; on a deploy, run each viewer (pnpm --filter @manicat/preview-vue build, then vite preview --port 3002) and, if they are not on 3001 to 3003 of the same machine, set VIEWER_ORIGIN_VUE, VIEWER_ORIGIN_SVELTE and VIEWER_ORIGIN_REACT_NATIVE.

Checks

pnpm check:registry keeps the formats from drifting apart:

  • every port file is published, and installs where it belongs;
  • the Svelte CSS equals the Vue CSS, and a classNames key exists in the same part of every web port;
  • every Swift token colour equals the React Native one in lib/rn-tokens.ts;
  • every Vue port compiles with vue/compiler-sfc.

Adding a port

You do not register a port anywhere. Put the file next to the React source and rebuild:

registry/components/button/
  button.tsx
  button.module.css
  button.rn.tsx     ← React Native
  button.vue        ← Vue
  button.swift      ← a new framework, found on the next build
pnpm --filter manicat build:registry

The build publishes public/r/button-swift.json, check:registry verifies it, and the site adds Swift to the switcher. See Contributing for the full checklist, including the optional usage snippet.

On this page