Uniwind and Gluestack put shadcn on React Native — but you still copy-paste per platform
shadcn/ui won the web by inverting the component-library model: don't npm install a black box, copy the source into your repo and own it. In 2026 that model finally reached React Native. Uniwind brings a full Tailwind v4, CSS-first config to native. Gluestack ships shadcn-style components for mobile. shadcn's own MCP wires Claude Code and Cursor straight into 6,000+ blocks. If you wanted shadcn on a phone, you can now have it.
So the gap is closed, right? Not quite. You can put shadcn-style components on web and on native — but you do it twice, with two different registries that don't share an API. You've moved the problem, not removed it.
What Uniwind and Gluestack actually give you
Credit where due: this is real progress, not vaporware.
- Uniwind lets you write Tailwind v4 utility classes in React Native with a CSS-first config — the same
classNameergonomics web developers already know, compiled to native styles. - Gluestack ships accessible, shadcn-shaped components (button, dialog, input, the usual set) built for Expo/React Native.
- shadcn MCP means an agent can pull blocks into your project on command, on either platform.
For a mobile-only project, this is great. You get the copy-paste-and-own ergonomics that made shadcn pleasant, now on iOS and Android. If that's your whole world, stop reading and go use them.
The trouble starts the moment you need the same product on web and native.
Copy-paste's superpower becomes a liability across platforms
The thing that makes the copy-paste model great on web — the code is yours, in your repo, no dependency — is exactly what makes it painful across two platforms. Because now you own two copies that have to stay in lockstep:
web/ → components/ui/button.tsx (shadcn — Radix + Tailwind, DOM)
native/ → components/ui/button.tsx (gluestack — RN primitives, native)Same name, two files, two registries, two upstreams, no shared API contract. When you add a loading prop to the web button, nothing reminds you the native button needs it too. When shadcn ships a fix, it lands in the web registry; gluestack's equivalent is a separate project on a separate release cadence. You're the synchronization layer, by hand, forever.
This is the cross-platform version of the .ios.tsx / .web.tsx fork — except worse, because the two implementations come from two different vendors with two different design philosophies. You're not maintaining one component on two platforms; you're maintaining two components and praying they look the same.

The part that breaks for AI agents
There's a second cost that wasn't obvious in the web-only era: a coding agent can't reason about a component whose two halves live in two unrelated registries.
Ask Claude Code to "add a destructive variant to Button everywhere." On a unified API, that's one change reflected on both platforms. With two registries, the agent edits the web button.tsx, can't find a matching contract on the native side, and either skips it or invents a different prop name. Now your <Button variant="destructive"> works on web and throws (or no-ops) on the phone, and you find out in TestFlight.
The agent isn't being dumb. It's reading two files that were never designed to agree, and there's no single source of truth telling it what Button is across platforms.
The missing middle
There are two stable shapes for cross-platform UI, and the market has both ends:
| Approach | What you get | What it costs |
|---|---|---|
| Two copy-paste registries (shadcn + gluestack) | Own the code; great per platform | You hand-sync two APIs forever |
| One universal library | One API, both platforms, versioned | A dependency (that you can still eject) |
The missing middle — until recently — was a library where <Button> is one component contract with two implementations underneath, installable or copy-pasteable, on web and native, from the same source of truth.
That's the shape of @otfdashkit/ui (web — Radix + Tailwind v4) and @otfdashkit/ui-native (native — Tamagui):
import { Button, Card, Input } from '@otfdashkit/ui' // web
import { Button, Card, Input } from '@otfdashkit/ui-native' // native// Same contract, both platforms — add a prop once, it exists everywhere:
<Button variant="destructive" loading={saving}>Delete</Button><Button> has one prop contract. The web and native implementations are guaranteed to agree because they're built and released together against the same API — not assembled from two vendors you sync by hand. And it ships both ways: pnpm add it, or copy the source via the CLI registry if you want shadcn-style ownership. You don't trade the copy-paste ergonomic for the unified API — you get both.

When each one actually wins
Honest tradeoffs, because "use our thing" is not an argument:
- Web only, forever? shadcn copy-paste is excellent. The two-registry problem never bites you because there's no second registry. Don't add a cross-platform abstraction you'll never use.
- Mobile only? Gluestack + Uniwind give you the shadcn feel on native. Same logic — no web half to sync.
- One product on web and native? This is where a single-API library earns its keep. The cost of a dependency is far smaller than the cost of being the human diff between two registries for the life of the project — especially when an agent is doing the edits and needs one contract to read.
The shadcn model is one of the best things to happen to web UI. It just doesn't stretch to two platforms on its own — and the tools bringing it to native, good as they are, leave you owning the stretch. If you're building for both, the question isn't "copy-paste or install" — both are fine — it's "one API or two." Two is a tax you pay every time you touch a component for as long as the product lives.