Tamagui Tailwind

Write Tailwind utility classes in className. The Tamagui compiler turns them into atomic CSS at build time, and the same string works on web and React Native.

Nate Wienert

·

·

10 min read

Tamagui Tailwind brings Tailwind utility classes to Tamagui’s className prop. The compiler turns them into atomic CSS at build time, and the same string renders on web and React Native from one source file.

Tailwind CSS is a trademark of Tailwind Labs Inc.; Tamagui is not affiliated with or endorsed by Tailwind Labs.

Choose the Tamagui Tailwind frontend by import:

import { Text, View, styled } from '@tamagui/tailwind'

Then write components with utility classes:

import { View } from '@tamagui/tailwind'
export function Card() {
return (
<View className="flex flex-row items-center gap-3 p-4 rounded-lg bg-slate-100">
<View className="w-10 h-10 rounded-full bg-blue-500" />
<View className="flex-1 h-2 rounded-md bg-slate-300" />
</View>
)
}

On web that compiles to atomic CSS. On iOS and Android the same classes resolve to React Native styles. No separate stylesheet, no Babel plugin specific to Tailwind, no second styling system to learn.

The problem

Two groups of people keep running into the same wall.

The first is teams who built a web app in Tailwind and now want to ship a React Native app that shares components. Tailwind is web-first. Moving a component to native means rewriting its styles in whatever the native styling layer expects, then keeping two copies in sync as the design changes.

The second is developers who already know Tailwind and would rather not learn a new prop API to get cross-platform output. Muscle memory for flex-row items-center gap-3 is real. Asking someone to relearn it as a different set of props is friction with no payoff for them.

Tamagui already solves the cross-platform styling problem. What it did not have was the Tailwind syntax on the front of it. @tamagui/tailwind adds that syntax without giving up the compiler, the theme system, or the component library underneath.

How the compiler works

When the compiler sees a className, it splits the string and maps each utility to a style. bg-blue-500 becomes a backgroundColor, p-4 becomes padding of 16px, rounded-lg becomes a border radius. Each resolved style is written as one atomic CSS rule and deduplicated across your whole app, so p-4 exists exactly once in the output no matter how many components use it.

Color and radius utilities resolve through your token system. bg-blue-500 looks up the Tailwind palette entry and emits a CSS variable reference, which means it participates in theming rather than hard-coding a hex value. Numeric spacing and sizing follow Tailwind’s scale, so p-4 is 16px and gap-2 is 8px. A color name that is not a palette token passes straight through as a literal CSS value, so bg-red resolves to backgroundColor: red.

Modifiers compile too. hover:bg-blue-600 extracts a hover rule into its own atomic class. sm:p-6 becomes a media-query rule. Combined modifiers like sm:hover:bg-purple-500 produce a single rule scoped to both the breakpoint and the pseudo-state. These are static, so they go in the stylesheet and cost nothing at runtime.

Classes the compiler does not recognize are left alone. If you write my-custom-class, it passes through to the rendered element’s className untouched, so your own CSS keeps working alongside the utilities.

Some values cannot become static rules, and the compiler is explicit about them. Physics springs, gesture-linked motion, and parent-driven native states need runtime state to resolve. CSS transitions with conditional opacity and transform values can compile on web, while truly dynamic values remain inline or use Tamagui’s runtime style path.

What compiles today

The utilities that resolve cross-platform cover the layout surface you reach for most: sizing (w-24, h-8, size-10, w-1/2, min-w-24, max-h-20), spacing (p-4, px-6, pt-8, m-4), flexbox and alignment (flex, flex-row, flex-1, items-center, justify-between, self-end, gap-2), typography (text-sm, text-white, text-center, text-[14px], font-bold, font-sans, leading-8, tracking-1), the full color palette for backgrounds and borders (bg-indigo-500, border-red-500), border radius including corners (rounded-md, rounded-t-xl, rounded-tl-lg, rounded-full), border width including sides (border, border-4, border-t-4, border-x), position (relative, absolute, inset-0, inset-x-0, inset-y-4, top-2, z-10), opacity (opacity-50), and overflow.

text-* follows Tailwind’s split: alignment keywords (text-center), the type scale (text-sm), palette and theme colors (text-white), and arbitrary lengths or colors (text-[14px], text-[#fff]). Unrecognized classes pass through on web and drop on native rather than throwing. Web-only leftovers (grid, float, backdrop filters, sibling space-x / divide-x) stay with the official Tailwind engine. The full remainder is in plans/v3-beta/tailwind-coverage.md.

Performance

The web benchmark renders 200 items, or 60 in the heavy case, in production Chromium on an Apple M5 Pro. Each arm gets two warmup rounds and 10 retained rounds. Seed 73129 reshuffles framework and scenario order each round, and a rendered-behavior gate runs before timing. The table reports mean milliseconds. Simple, rich, group, and heavy use Tamagui beta 901. Animated uses beta 917.

ScenarioOperationTamagui Tailwind compiledTailwind CSSInline ReactNativeWind v5Uniwind
SimpleMount0.450.350.531.601.71
SimpleUpdate0.960.980.902.432.35
SimpleRemount0.550.440.631.721.80
RichMount0.570.330.901.731.64
RichUpdate1.141.061.072.392.59
RichRemount0.670.430.981.822.11
GroupMount0.710.801.384.215.24
GroupUpdate2.232.532.186.006.92
GroupRemount0.921.101.835.756.10
HeavyMount0.720.631.012.953.11
HeavyUpdate1.671.801.704.524.42
HeavyRemount1.060.931.343.594.00
AnimatedMount0.420.310.571.211.07
AnimatedUpdate1.911.141.251.961.81
AnimatedRemount2.011.982.352.812.95
Scenario
Simple
Operation
Mount
Tamagui Tailwind compiled
0.45
Tailwind CSS
0.35
Inline React
0.53
NativeWind v5
1.60
Uniwind
1.71
Scenario
Simple
Operation
Update
Tamagui Tailwind compiled
0.96
Tailwind CSS
0.98
Inline React
0.90
NativeWind v5
2.43
Uniwind
2.35
Scenario
Simple
Operation
Remount
Tamagui Tailwind compiled
0.55
Tailwind CSS
0.44
Inline React
0.63
NativeWind v5
1.72
Uniwind
1.80
Scenario
Rich
Operation
Mount
Tamagui Tailwind compiled
0.57
Tailwind CSS
0.33
Inline React
0.90
NativeWind v5
1.73
Uniwind
1.64
Scenario
Rich
Operation
Update
Tamagui Tailwind compiled
1.14
Tailwind CSS
1.06
Inline React
1.07
NativeWind v5
2.39
Uniwind
2.59
Scenario
Rich
Operation
Remount
Tamagui Tailwind compiled
0.67
Tailwind CSS
0.43
Inline React
0.98
NativeWind v5
1.82
Uniwind
2.11
Scenario
Group
Operation
Mount
Tamagui Tailwind compiled
0.71
Tailwind CSS
0.80
Inline React
1.38
NativeWind v5
4.21
Uniwind
5.24
Scenario
Group
Operation
Update
Tamagui Tailwind compiled
2.23
Tailwind CSS
2.53
Inline React
2.18
NativeWind v5
6.00
Uniwind
6.92
Scenario
Group
Operation
Remount
Tamagui Tailwind compiled
0.92
Tailwind CSS
1.10
Inline React
1.83
NativeWind v5
5.75
Uniwind
6.10
Scenario
Heavy
Operation
Mount
Tamagui Tailwind compiled
0.72
Tailwind CSS
0.63
Inline React
1.01
NativeWind v5
2.95
Uniwind
3.11
Scenario
Heavy
Operation
Update
Tamagui Tailwind compiled
1.67
Tailwind CSS
1.80
Inline React
1.70
NativeWind v5
4.52
Uniwind
4.42
Scenario
Heavy
Operation
Remount
Tamagui Tailwind compiled
1.06
Tailwind CSS
0.93
Inline React
1.34
NativeWind v5
3.59
Uniwind
4.00
Scenario
Animated
Operation
Mount
Tamagui Tailwind compiled
0.42
Tailwind CSS
0.31
Inline React
0.57
NativeWind v5
1.21
Uniwind
1.07
Scenario
Animated
Operation
Update
Tamagui Tailwind compiled
1.91
Tailwind CSS
1.14
Inline React
1.25
NativeWind v5
1.96
Uniwind
1.81
Scenario
Animated
Operation
Remount
Tamagui Tailwind compiled
2.01
Tailwind CSS
1.98
Inline React
2.35
NativeWind v5
2.81
Uniwind
2.95

On beta 901, compiled Tamagui Tailwind had lower mount means than NativeWind and Uniwind in simple, rich, group, and heavy. The paired 95% confidence intervals for all eight comparisons were below zero. Its whole-app JavaScript was 111,808 bytes gzipped, with 47,925 bytes attributed to the framework and 1,093 bytes of CSS gzip. Those bundle figures also use beta 901.

The animated fixture uses a CSS transition with conditional opacity and transform values. On beta 917 the compiler found, lowered, and flattened all 14 View candidates in the fixture source with zero bailouts. Tamagui Tailwind compiled measured 0.42ms mount, 1.91ms update, and 2.01ms remount.

Direct paired effects report Tamagui Tailwind minus each rival. Against NativeWind, the differences were -0.79ms for mount (95% CI -0.988 to -0.592), -0.05ms for update (-0.376 to 0.276), and -0.80ms for remount (-1.066 to -0.534). Against Uniwind, they were -0.65ms (-0.741 to -0.559), 0.10ms (-0.176 to 0.376), and -0.94ms (-1.445 to -0.435). Tamagui Tailwind was lower for mount and remount against both native utility libraries, while neither update interval resolved a difference.

Against Tailwind CSS, the paired differences were 0.11ms for mount (0.001 to 0.219), 0.77ms for update (0.529 to 1.011), and 0.03ms for remount (-0.256 to 0.316). Tailwind CSS was lower for mount and update; the remount interval did not resolve a difference. These are results for this fixture on this machine, not a universal framework ranking.

The beta 901 compiler build flattened the group and heavy fixture elements. It left the heavy fixture’s computed widths as dynamic values while extracting the static utility work. Its one source-wide bailout was the older animated conditional. Beta 917 closed that animated bailout without changing the fixture. The benchmark covers a CSS transition, not a physics spring or gesture-linked animation.

Cross-platform coverage

The beta 891 coverage harness derives all 23,286 candidates from Tailwind CSS 4.3.0’s installed utility registry instead of a hand-maintained list. It runs each library’s parser or compiler on both targets.

FrameworkWeb resolvedNative stylesNative share of registry
Tamagui Tailwind23,2864,52219.42%
NativeWind v523,28620,50088.04%
Uniwind23,28622,35295.99%
Framework
Tamagui Tailwind
Web resolved
23,286
Native styles
4,522
Native share of registry
19.42%
Framework
NativeWind v5
Web resolved
23,286
Native styles
20,500
Native share of registry
88.04%
Framework
Uniwind
Web resolved
23,286
Native styles
22,352
Native share of registry
95.99%

Tamagui Tailwind resolves all 23,286 classes on web. Of those, 4,522 become Tamagui styles and 18,764 pass through to CSS. The same 4,522 return React Native styles on native. Beta 891 closed the named text color and size, font, size, corner radius, single-side border, and axis inset gaps. Its largest remaining native gaps are masks, gradients, shadows, rings, outlines, and logical-direction borders.

The registry contains web-only CSS, so these percentages describe each compiler’s accepted native surface. They do not claim that React Native can visually reproduce every CSS utility.

One component, both platforms

This renders on web and native from the same file. No platform branches, no second stylesheet.

import { View } from '@tamagui/tailwind'
export function ListItem() {
return (
<View className="flex flex-row items-center gap-3 p-3 rounded-xl bg-slate-100">
<View className="w-12 h-12 rounded-full bg-indigo-500" />
<View className="flex-1 flex flex-col gap-2">
<View className="h-3 w-2/3 rounded-md bg-slate-400" />
<View className="h-2 w-1/3 rounded-md bg-slate-300" />
</View>
<View className="w-8 h-8 rounded-lg bg-blue-500" />
</View>
)
}

On web that compiles to atomic CSS classes in the bundle. On iOS and Android the same string resolves to React Native styles. The visual output matches pixel for pixel against real Tailwind for the utilities in our conformance suite, which is how we verify the native mapping rather than trusting a screenshot.

Regular Tamagui and Tailwind components can share the same tree. Each component keeps one styling language, selected by its import:

import { View as TamaguiView } from 'tamagui'
import { View as TailwindView } from '@tamagui/tailwind'
export function Highlight() {
return (
<TamaguiView backgroundColor="blue-5 hover:blue-6">
<TailwindView className="rounded-lg p-4" />
</TamaguiView>
)
}

Theme tokens like blue-5 respond to light and dark mode the way they always have. Both components still use the shared renderer, themes, and platform behavior.

Getting started

Install the latest Tamagui and the v6 config, which ships the Tailwind palette, the named radii, and the w/h shorthands the utility parser needs:

yarn add tamagui@latest @tamagui/tailwind@latest @tamagui/config@latest

Keep your ordinary Tamagui config and import the Tailwind components where you want utility authoring:

import { View } from '@tamagui/tailwind'

Write a component:

import { View } from '@tamagui/tailwind'
export function Badge() {
return (
<View className="px-3 py-1 rounded-full bg-green-500">
<View className="w-4 h-4 rounded-full bg-white" />
</View>
)
}

Build it. The compiler extracts the CSS, and the component renders on web and native with the static styles already resolved.

If you are on Tailwind today and want native output without rewriting your styles, this is the path. The compiler, the themes, the animation drivers, and the component library are all still there when you reach for them.