shadniwind is a collection of shadcn-style, source-distributed UI components specifically built for React Native and React Native Web.
Unlike traditional component libraries, shadniwind components are provided as source code that you own and can customize. It is built from the ground up to leverage the performance and flexibility of react-native-unistyles v3.
- Source Distributed: Just like shadcn/ui, you copy the components into your project. You have full control.
- Unistyles v3: Leverages the latest Unistyles for high-performance styling, theming, and responsive design.
- Native-First: Optimized for React Native (iOS/Android) while maintaining excellent Web support.
- Nitro-Powered: Uses
react-native-nitro-modulesfor ultra-fast bridge communication.
shadniwind is designed for Universal Apps. You write your application code once using React Native primitives (View, Text, etc.), and it renders to the appropriate target:
- Mobile (iOS/Android): Renders true native UI components.
- Web: Uses
react-native-webto render standard HTML/CSS/DOM elements.
You don't need to maintain separate projects. With the recommended Expo setup, you build one app and run it on multiple platforms:
npx expo run:ios(Runs on iOS Simulator/Device)npx expo run:android(Runs on Android Emulator/Device)npx expo start --web(Runs in your browser)
shadniwind components handle platform-specific nuances (like hover states on web vs. touch handling on native) internally, so you can focus on building features.
To use shadniwind, your environment must meet these specifications:
- React Native 0.78+: With the New Architecture enabled.
- Expo SDK 53+: Using the dev client or prebuild flow. Expo Go is not supported due to native dependencies.
- Dependencies:
react-native-nitro-modulesreact-native-edge-to-edgereact-native-unistyles v3
Follow these steps to initialize a new project with shadniwind.
Because shadniwind uses native dependencies (react-native-nitro-modules, react-native-edge-to-edge), you must use a Development Build. Expo Go is not supported.
Create a new Expo project:
npx create-expo-app@latest my-app
cd my-app
npx expo run:ios # or run:androidThe components.json file tells the shadcn CLI how to install components. Create this file in your project root:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "",
"baseColor": "zinc",
"cssVariables": false
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {
"@shadniwind": "https://deicod.github.io/shadniwind/v1/r/{name}.json"
}
}Key settings: Registry names must be prefixed with @, and custom registry URLs must include {name}.
You can also add the registry via CLI:
npx shadcn@latest registry add "@shadniwind=https://deicod.github.io/shadniwind/v1/r/{name}.json"Install the tokens package. This sets up the base theme (colors, spacing, typography) and the styling engine configuration.
npx shadcn@latest add @shadniwind/tokensNote: This will install react-native-unistyles and create lib/tokens.ts, lib/unistyles.ts, and lib/unistyles-types.d.ts.
shadniwind components use the Unistyles v3 authoring model (StyleSheet.create((theme) => ...), variants, and runtime theme access). Add the Unistyles Babel plugin so files in your app, components/, and lib/ are processed correctly.
Use app-scoped absolute paths in autoProcessPaths. Do not pass bare folder names like "components" because the Unistyles plugin matches with filename.includes(...) and can accidentally process files in node_modules.
babel.config.js:
const path = require("node:path");
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
[
"react-native-unistyles/plugin",
{
root: "app",
autoProcessPaths: [
path.join(__dirname, "components"),
path.join(__dirname, "lib"),
],
},
],
],
};
};If you already have a Babel config, merge the Unistyles plugin into it instead of replacing your existing plugins.
If your app keeps Unistyles-authored files in other local directories, add those with path.join(__dirname, "...") too.
You must initialize Unistyles before Expo Router or your app modules import components that call StyleSheet.create.
For Expo Router:
Update your entry point so Unistyles initializes before Expo Router resolves your routes.
package.json:
{
"main": "index.ts"
}index.ts:
import "./lib/unistyles";
import "expo-router/entry";If you use Expo static web output, also add a root HTML file so Unistyles initializes during static rendering and emits its SSR CSS/hydration payload on the server render.
app/+html.tsx:
import { ScrollViewStyleReset } from "expo-router/html";
import type { PropsWithChildren } from "react";
import { useServerUnistyles } from "react-native-unistyles";
import "../lib/unistyles";
export default function Root({ children }: PropsWithChildren) {
const serverUnistyles = useServerUnistyles({ includeRNWStyles: false });
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<ScrollViewStyleReset />
{serverUnistyles}
</head>
<body>{children}</body>
</html>
);
}Expo Router already handles the React Native Web stylesheet during export, so pass includeRNWStyles: false and let useServerUnistyles inject only the Unistyles SSR tags.
For Bare React Native (App.tsx):
import "./lib/unistyles";
import React from "react";
// ...Install the portal primitive if you use components that float above your content, like Dialogs, Popovers, Tooltips, Drawers, and Sheets. Registry dependencies will install the portal files automatically for those components, but you still need to wire the root PortalProvider and PortalHost once.
npx shadcn@latest add @shadniwind/portalWrap your application root with PortalProvider and add a PortalHost.
For Expo Router (app/_layout.tsx):
import { Slot } from "expo-router";
import { PortalHost, PortalProvider } from "@/lib/portal";
export default function RootLayout() {
return (
<PortalProvider>
<Slot />
<PortalHost />
</PortalProvider>
);
}You can now start adding components:
npx shadcn@latest add @shadniwind/buttonThen use it in your code:
import { Button } from '@/components/ui/button';
export function HomeScreen() {
return (
<Button onPress={() => console.log('Pressed!')}>
Hello World
</Button>
);
}