Create reusable React Native + NativeWind components with Tailwind CSS syntax.
npm install react-native-twc> Create reusable React Native + NativeWind components with Tailwind CSS syntax
A lightweight library for creating styled React Native components using Tailwind CSS class syntax with NativeWind support. Inspired by TWC (react-twc).
- β‘οΈ Lightweight β only ~1KB minified
- β¨ Full TypeScript support with autocompletion
- π¨ Dynamic styling based on props
- π¦ Works with any React Native component
- π First-class tailwind-merge and cva support
- π± Built for React Native + NativeWind
- π withChildren β Pre-define children rendering with type safety
- π Smart style merging β attrs styles merge with props styles
``bashnpm
npm install react-native-twc
$3
Make sure you have the following peer dependencies installed:
`bash
npm install react react-native nativewind tailwind-merge
`Usage
$3
Without
twc:`tsx
import * as React from "react";
import { View, Text, ViewProps } from "react-native";
import clsx from "clsx";const Card = React.forwardRef(({ className, ...props }, ref) => (
ref={ref}
className={clsx("rounded-lg border bg-slate-100 p-4 shadow-sm", className)}
{...props}
/>
));
`With
twc:`tsx
import { View } from "react-native";
import { twc } from "react-native-twc";const Card = twc(View)
rounded-lg border bg-slate-100 p-4 shadow-sm;
`$3
`tsx
import { View, Text, Pressable, TextInput } from "react-native";
import { twc } from "react-native-twc";// Basic styled components
const Container = twc(View)
flex-1 bg-white p-4;
const Title = twc(Text)text-2xl font-bold text-gray-900;
const Subtitle = twc(Text)text-lg text-gray-600;// Styled input
const Input = twc(TextInput)
border border-gray-300 rounded-lg px-4 py-2;// Styled button
const Button = twc(Pressable)
bg-blue-500 py-3 px-6 rounded-lg;
const ButtonText = twc(Text)text-white font-semibold text-center;
`$3
Use the
$ prefix for transient props that won't be passed to the underlying component:`tsx
import { Pressable, Text } from "react-native";
import { twc, TwcComponentProps } from "react-native-twc";type ButtonProps = TwcComponentProps & {
$variant?: "primary" | "secondary" | "danger";
};
const Button = twc(Pressable)((props) => [
"py-3 px-6 rounded-lg font-semibold",
{
"bg-blue-500": props.$variant === "primary",
"bg-gray-200": props.$variant === "secondary",
"bg-red-500": props.$variant === "danger",
},
]);
// Usage
`$3
Add default props to your components:
`tsx
import { TextInput } from "react-native";
import { twc } from "react-native-twc";const EmailInput = twc(TextInput).attrs({
keyboardType: "email-address",
autoCapitalize: "none",
placeholder: "Enter your email",
})
border border-gray-300 rounded-lg px-4 py-2;// Usage
console.log(text)} />
`$3
`tsx
import { Pressable } from "react-native";
import { cva, VariantProps } from "class-variance-authority";
import { twc, TwcComponentProps } from "react-native-twc";const buttonVariants = cva("rounded-lg font-semibold", {
variants: {
$intent: {
primary: "bg-blue-500 text-white",
secondary: "bg-gray-200 text-gray-800",
danger: "bg-red-500 text-white",
},
$size: {
sm: "py-1 px-2 text-sm",
md: "py-2 px-4 text-base",
lg: "py-3 px-6 text-lg",
},
},
defaultVariants: {
$intent: "primary",
$size: "md",
},
});
type ButtonProps = TwcComponentProps &
VariantProps;
const Button = twc(Pressable)(({ $intent, $size }) =>
buttonVariants({ $intent, $size })
);
// Usage
`$3
Use the built-in
twx for automatic class conflict resolution:`tsx
import { Text } from "react-native";
import { twx } from "react-native-twc";const Title = twx(Text)
font-bold text-lg;// Later classes override earlier ones
Hello
// Result: "font-normal text-sm" (conflicts resolved)
`Or create your own instance:
`tsx
import { twMerge } from "tailwind-merge";
import { createTwc } from "react-native-twc";const twc = createTwc({ compose: twMerge });
`$3
By default, props starting with
$ are not forwarded. Customize this behavior:`tsx
import { View } from "react-native";
import { twc, TwcComponentProps } from "react-native-twc";type Props = TwcComponentProps & { size: "sm" | "lg" };
const Box = twc(View).transientProps(["size"])((props) => ({
"w-4 h-4": props.size === "sm",
"w-8 h-8": props.size === "lg",
}));
// 'size' won't be passed to the underlying View
`$3
Pre-define how children should be rendered. Useful for creating button components with consistent text styling:
`tsx
import { Pressable, Text } from "react-native";
import { twx } from "react-native-twc";// Default: accepts any ReactNode
const Card = twx(View).withChildren((children) => (
{children}
))
bg-white rounded-lg;// With generic type: accepts only string
const Button = twx(Pressable).withChildren((text) => (
{text}
))
bg-blue-500 py-3 px-6 rounded-lg;// Usage
// text is typed as string
// children is ReactNode
`Combine with
attrs for powerful component composition:`tsx
const FloatButton = twx(Pressable)
.attrs({
activeOpacity: 0.8,
style: { shadowColor: "#000", shadowOffset: { width: 0, height: 2 } },
})
.withChildren((text) => (
{text}
)) absolute bottom-4 right-4 bg-purple-500 rounded-full p-4;Add
`$3
When using
attrs with a style prop, styles are intelligently merged (not replaced) when you pass additional styles:`tsx
const Card = twc(View).attrs({
style: { backgroundColor: "white", padding: 16, borderRadius: 8 },
})shadow-lg;// Styles are merged: padding and borderRadius are preserved
Content
// Result: { backgroundColor: "blue", padding: 16, borderRadius: 8, margin: 10 }
`This also works with dynamic attrs:
`tsx
type Props = TwcComponentProps & { $padded?: boolean };const Box = twc(View).attrs((props) => ({
style: { padding: props.$padded ? 20 : 0 },
}))
bg-gray-100;Content
// Result: { padding: 20, margin: 10 }
`API Reference
$3
Wraps a React Native component and returns a template function.
`tsx
const StyledView = twc(View)bg-white p-4;
`$3
Creates a custom TWC instance with configuration options.
`tsx
const twc = createTwc({
compose: twMerge, // Custom class merging function
shouldForwardProp: (prop) => !prop.startsWith("_"), // Custom prop filtering
});
`$3
Utility type to extract props from a TWC-wrapped component.
`tsx
type CardProps = TwcComponentProps;
`$3
Utility function combining
clsx and tailwind-merge.`tsx
import { cn } from "react-native-twc";const className = cn("p-4", condition && "bg-blue-500", ["rounded", "shadow"]);
`$3
Pre-configured TWC instance using
tailwind-merge for class conflict resolution.`tsx
import { twx } from "react-native-twc";const Title = twx(Text)
font-bold;
`$3
Pre-define children rendering with optional type constraint.
`tsx
// Accept any ReactNode (default)
const Card = twc(View).withChildren((children) => (
{children}
))bg-white;// Accept only string
const Button = twc(Pressable).withChildren((text) => (
{text}
))
bg-blue-500;// Accept string or undefined
const Label = twc(View).withChildren((text) => (
{text ?? "Default"}
))
p-2;
`$3
When
attrs includes a style prop, it will be merged with any style passed to the component (not replaced):`tsx
const Box = twc(View).attrs({
style: { padding: 10 }, // Base style
})bg-white; // Merged: { padding: 10, margin: 5 }
`Differences from TWC (Web)
| Feature | TWC (Web) | react-native-twc |
|---------|-----------|------------------|
| HTML tags (
twc.div) | β
Supported | β Not supported |
| asChild prop | β
Supported | β Not supported |
| withChildren` | β Not supported | β
Supported |- TWC (react-twc) by Greg BergΓ© β The original inspiration for this project
- NativeWind β Tailwind CSS for React Native
- styled-components β Where the template literal API originated
- tailwind-merge β Intelligent Tailwind class merging
MIT