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.
| Framework | File | Customise parts with | Installs |
|---|---|---|---|
| React | button.tsx | classNames | @manicat/button |
| React Native | button.rn.tsx | styles | @manicat/button-react-native |
| Vue | button.vue | classNames | @manicat/button-vue |
| Svelte | button.svelte | classNames | @manicat/button-svelte |
| Swift | button.swift | styles | @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.
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-swiftReact 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,TabsListandTabsTriggerare Radix parts. The ports take the same information as data instead:items(plus a slot per tab in Vue, acontentfield in React Native).Dialogtakes atrigger(a#triggerslot in Vue) andopen/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,accessibilityStateand 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.
| Framework | Command | Port |
|---|---|---|
| Svelte | pnpm --filter @manicat/preview-svelte dev | 3001, at /preview/svelte/ |
| Vue | pnpm --filter @manicat/preview-vue dev | 3002, at /preview/vue/ |
| React Native | pnpm --filter @manicat/preview-rn dev | 3003, at /preview/react-native/ (react-native-web, in a phone frame) |
| Swift | cd apps/preview-swift && swift run | a 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
classNameskey 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 buildpnpm --filter manicat build:registryThe 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.