Skip to content

sagltd/rnwind

Repository files navigation

rnwind

Tailwind CSS v4 for React Native — compiled at build time, zero runtime parsing.

Build Code Quality MIT Runtime 100% 659 tests Bun

Quickstart · Demo · Docs · Architecture

<View className="flex-1 p-4 bg-bg md:flex-row md:gap-4">
  <Text className="text-lg text-fg font-semibold dark:text-white">Hello</Text>
</View>

A Metro babel transformer compiles every className at build time into a hoisted atom array (['flex-1', 'p-4', 'bg-bg', …]). At runtime, lookupCss walks the array once per hoist and caches the resolved style-object array in a WeakMap — same render → same array reference, so React Native's style diff short-circuits and no native-view update fires unless atoms actually changed.

No runtime Tailwind parser. No regex on the hot path. No var(--…) left unresolved. Theme tokens, scheme variants, and breakpoint thresholds are all baked into the generated *.style.js files at build time.


Showcase

Dark scheme — palette + utilities Brand scheme — runtime switch Animations — enter/loop/repeat Interactive variants — active/focus + haptics
Schemes
palette + utilities
Runtime switch
light · dark · brand
Animations
enter / loop / repeat
Interactive
active: / focus: + haptics

From the examples/expo-go demo app — every screen is a real rnwind component.


Features

  • 🎨 Real Tailwind v4@tailwindcss/oxide, every utility, every variant
  • 🌓 Any number of color schemes@variant blocks → typed Scheme union
  • 📱 Responsive designsm: md: lg: xl: 2xl: + your own breakpoints
  • Reanimated v4 animationsenter-* exit-* loop-* repeat-*
  • 🛡️ Safe-area utilities*-safe resolved at render against live insets
  • Zero-parse runtimeWeakMap cache, stable array refs, no JS-thread cost
  • 🧪 Real E2E testsrnwind/testing on @testing-library/react-native

Quickstart

bun add rnwind tailwindcss
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const { withRnwindConfig } = require('rnwind/metro')

module.exports = withRnwindConfig(getDefaultConfig(__dirname), {
  cssEntryFile: './global.css',
})
/* global.css */
@import 'tailwindcss';
@import 'rnwind/css';

@custom-variant light (&:where(.scheme-light, .scheme-light *));
@custom-variant dark  (&:where(.scheme-dark, .scheme-dark *));

@theme {
  --color-bg: #f8fafc;
  --color-fg: #0f172a;
}
@layer theme {
  :root {
    @variant dark { --color-bg: #0b1120; --color-fg: #f8fafc; }
  }
}
// app/_layout.tsx
import { RnwindProvider } from 'rnwind'
import { useColorScheme } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

export default function Root({ children }) {
  const system = useColorScheme()
  const insets = useSafeAreaInsets()
  return (
    <RnwindProvider scheme={system === 'dark' ? 'dark' : 'light'} insets={insets}>
      {children}
    </RnwindProvider>
  )
}
// any component
<View className="flex-1 p-4 bg-bg md:flex-row">
  <Text className="text-fg dark:text-white">Hello</Text>
</View>

Read context with useRnwind

import { useRnwind } from 'rnwind'

function StatusBar() {
  const { scheme, activeBreakpoint, windowWidth, insets } = useRnwind()
  return (
    <Text className="text-xs text-muted">
      {scheme} · {activeBreakpoint} · {windowWidth}px · top inset {insets.top}
    </Text>
  )
}

useRnwind() is the single context read — destructure what you need (scheme, activeBreakpoint, windowWidth, fontScale, insets, tables, onHaptics). Every value is reactive.

That's it. Detailed setup & options: docs/setup.md.


Run the demo

A fully-featured Expo example lives in examples/expo-go. It exercises every feature: schemes, responsive, animations, safe-area, interactive variants, haptics.

# from repo root
bun install
bun run build
cd examples/expo-go
bun install
bun run ios       # or: bun run android  /  bun run web

First run takes ~30s while Tailwind oxide scans + the cache materialises. Subsequent saves Fast-Refresh in <100ms.

The example covers four screens:

  • app/index.tsx — utilities, schemes, responsive, safe-area
  • app/animations.tsxenter-* / loop-* / repeat-*
  • app/interact.tsxactive: / focus: + haptics
  • app/transitions.tsxlayout-* shared transitions

Useful debug scripts:

bun run dump:index     # see what the transformer emits for app/index.tsx
bun run inspect        # build a dev bundle to .bundle-inspect/
bun run build:ios      # production export to .prod-bundle/

Why rnwind

What it commits to:

  • Real Tailwind v4 — every utility, variant, and @utility from @tailwindcss/oxide. No DSL drift, no subset.
  • Build-time only. Production bundles ship zero Tailwind code. The runtime is ~3KB of WeakMap lookups.
  • Reanimated v4 CSS animations out of the box. enter-* / exit-* / loop-* / repeat-* compile straight to keyframes Reanimated runs on the UI thread.
  • Any number of color schemes (@variant brand, @variant high-contrast, …), not just light/dark. Scheme names flow into TS as a literal union.
  • Mobile-first responsive with full Tailwind defaults (sm / md / lg / xl / 2xl) + --breakpoint-* overrides. Reactive via useWindowDimensions().width.
  • First-class testing. rnwind/testing runs the real transformer + runtime in your test process, on @testing-library/react-native.

Other RN-Tailwind libraries have their own strengths — try them, pick what fits your app. rnwind's bet is on a strict build-time pipeline + a tiny stable runtime.


Documentation

docs/responsive.md sm: / md: / lg:, custom breakpoints, activeBreakpoint
docs/schemes.md @variant blocks, runtime switch, typed Scheme
docs/animations.md enter-*, exit-*, loop-*, repeat-*, custom @keyframes
docs/safe-area.md *-safe, *-safe-or-N, h-screen-safe, provider wiring
docs/interact.md active: / focus: variants, chainPress, haptics
docs/api.md Full API reference — hooks, components, low-level
docs/testing.md renderWithCss, renderHookWithCss, Bun + Jest setup
docs/setup.md Metro options, monorepos, IntelliSense, .d.ts generation
docs/architecture.md Full pipeline — parser, builder, runtime, transformer

Non-goals

  • No runtime Tailwind. Your production bundle does not ship @tailwindcss/oxide, lightningcss, or tailwindcss.
  • No CSS cascade. Every className resolves to a flat { property: value } map at compile time; precedence is class-list order (last wins on RN flatten).
  • No var() at runtime. Theme variables resolve during compile, per scheme.
  • No selector combinators. &:hover > * doesn't map to RN. active: / focus: work via matching RN props; sm: / md: / etc. work via window-width gating.

Development

bun install
bun run build        # rollup → packages/rnwind/lib
bun run test         # all packages
bun run code-check   # typecheck + lint + test (CI gate)

Bun-only. No npm, no tsx.


Quickstart · Demo · Docs · Architecture

MIT · Built with ♥ and Bun

About

Tailwind CSS v4 for React Native — compiled at build time, zero runtime parsing.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors