Move StyleSheet to Tailwind.

Prove every class.

A codemod that rewrites React Native StyleSheet.create styles as className, and writes a class only when Tailwind gives back the exact same style. Agents run it in one command instead of editing file by file, so a migration costs about half the tokens.

$npx react-native-stylesheet-to-tailwind

Dry run first. Shows the diff, writes nothing.

npx react-native-stylesheet-to-tailwind --json
{
  "mode": "dry-run",
  "libraries": ["uniwind"],
  "files": { "scanned": 9115, "changed": 520 },
  "styles": {
    "moved": 258, "split": 721,
    "stayed": 2049, "arbitrary": 232
  },
  "stays": [
    { "file": "apps/web/app-frame.tsx",
      "line": 29,
      "reason": "no-exact-class" }
  ]
}

Each style ends one of three ways

movedEvery property became a proven class. The style prop is gone.
splitThe proven part moved to className. The rest stays in style.
stayedLeft in StyleSheet.create, listed with a reason so a person or agent can decide.

Half the tokens for the agent.

None of the guessing.

Two agents, same model and goal, converted the same 10 StyleSheet files from a real Uniwind monorepo. One edited by hand. The other ran the codemod.

49%

fewer tokens

50,326 to 25,568

67%

less time

55.2 s to 18.0 s

63%

fewer tool calls

8 to 3

0

guessed classes

manual agent 4, codemod 0

The manual agent moved more styles, 11 to 3. Four of its classes were guesses: a radius each app sets at runtime, and theme colors it never checked. The codemod left those in place with a reason.

The saving grows with the file count. Most of the codemod agent's cost is fixed starting context. This is one run, not a study: run your own with --json.

Paste a component.

Watch the proof run.

This is the real codemod, running in your browser. Nothing is sent anywhere. Both sides render below and every element's computed style is compared.

Example
Invite card (default)
Press state
Pressed array becomes active class
Pressed callback that reads hovered stays
Pressed ternary becomes plain and active classes
Conditions and class merge
And member becomes class merge argument
Class merge import is not added twice
Conditional without class merge stays
File with its own cn name stays
Plain use needs no class merge
Static array flattens
Style const with condition uses class merge
Ternary becomes class merge ternary
Ternary with empty branch becomes class merge ternary
Two conditions become two class merge arguments
Components
Color prop on host component stays
Comment above component is kept
Content container style becomes class name
Existing static class name gets classes added
Imported component ignoring class name stays
Imported component reading class name gets class name
Imported component with base style stays
Linear gradient has no class name
Own component with class name prop gets class name
Own component without class name prop stays
Reanimated view gets class name
Style order and props
Caller style before sheet style flips order
Caller style still wins
Props after style are kept
Props before style are kept
Self closing and wrapping elements
Values to classes
Absolute fill stays unchanged
Absolute position with offsets
Border width and color
Card rule moves to classes
Center content
Color names become background and border classes
Elevation has no class
Flex row with gap
Flex wrap and align self
Font size 17 without arbitrary values has no class
Font size alone is arbitrary
Font size with line height is text size
Hairline width stays dynamic
Left and right margins become logical
Margin and margin horizontal conflict
Module constant width has no exact class
Negative margin
Opacity half
Overflow hidden
Padding and padding top conflict
Padding left and right in one rule stay as family conflict
Percent width
Rounded full border radius
Shadow offset has no class
Split keeps dynamic width in style
Text align center
Text style moves on text
Theme color becomes token class
Zero padding
Files, sheets and style consts
Color name becomes theme class
Color name missing from theme stays
Create styles wrapper and hook are removed
Empty rule is unchanged
Exported sheet is skipped
File without style sheet is unchanged
Flatten call keeps rule
Inline style object without sheet stays unchanged
Nested color name becomes theme class
One rule used twice moves both
Platform select stays
Scheme wrapper stays unchanged
Sheet with no uses is unchanged
Style const passed to function stays
Style const used only as style becomes class name const
Style property read in code keeps rule
Style without sheet reference unchanged
Two rules two elements
Unmapped color stays while mapped color moves
Untouched lines keep their spacing
Unused sheet rule is kept when one rule moves
Loading
Theme tokens @theme { }
BeforePaste or type. Runs as you edit.
AfterclassName
Before renders
After renders
Checking
0 moved0 split0 stayed0 arbitrary

No class is written

on a guess.

Read your Tailwind entry

It loads your CSS entry with @tailwindcss/node, so your own tokens like text-brand and rounded-card are known.

@import "tailwindcss";
@theme { --color-brand: #0a84ff; }

Turn the class back into a style

For each style it picks a candidate class, asks Tailwind for its CSS, and reads that CSS back as a React Native style.

bg-brand
  -> background-color: #0a84ff
  -> { backgroundColor: "#0a84ff" }

Write it only on an exact match

The class is written only when both styles are equal. Otherwise the property stays in StyleSheet.create.

{ backgroundColor: "#0a84ff" }
{ backgroundColor: "#0a84ff" }  equal, write

Every style left behind

comes with a reason.

With --json they arrive in the stays list, so an agent spends its tokens only where a decision is needed.

component-has-no-class-name

The component does not take a className.

dynamic-value

The value is worked out at runtime.

used-outside-style-prop

The style is also read outside a style prop.

conditional-without-class-merge

The style is conditional and no class-merge function was found.

family-conflict

The classes would fight over the same property.

style-order-would-flip

Moving it would change which style wins.

no-exact-class

No Tailwind class gives the exact same value.

scheme-wrapper

The styles are built by a function that reads the color scheme.

no-css-entry-covers-file

No working CSS entry of a Uniwind or NativeWind app covers this file.

79 cases, each

rendered twice.

Press states, conditions, components, style order, values and files. Each case shows the code before and after, renders both, and checks that they match.

Questions

Will my app look different after the run?

No. A class is written only when the style Tailwind makes from it equals the original style exactly. Anything else stays in StyleSheet.create.

Why did some styles stay?

Each style that stays is listed with a reason. With --json they come in the stays list.

How do I undo a run?

--write refuses to run on a git tree with uncommitted changes unless you pass --allow-dirty, so the run is one clean diff. Review it with git diff and undo it with git restore ..

Can I convert only part of the app?

Yes, with --include <glob> and --exclude <glob>, as many times as needed.

Why does it write classes like text-[16px]?

When no theme class gives the exact value, an arbitrary value class is the only exact match. --no-arbitrary-values keeps those styles in StyleSheet.create instead.

How are conditional styles handled?

style={[styles.base, isActive && styles.active]} needs a class-merge function such as cn. The codemod uses the one your files import most, or the one you pass with --class-merge. Without one the style stays with the reason conditional-without-class-merge.

Does it work in a monorepo?

Yes. Each file is checked against its own package and the Tailwind CSS entry that covers it.

Does it support NativeWind 4 or dark mode?

Not yet. NativeWind 4 runs on Tailwind 3, which is not supported. Styles built inside a function that reads the color scheme stay with the reason scheme-wrapper.

Is my code sent anywhere?

No. Everything runs on your machine and the codemod makes no network calls. The playground on this page runs in your browser.

Can a CI job or an AI agent run it?

Yes. --json prints one JSON object with no colors or prompts, and --write --yes writes without a terminal.

Browse cases 79

Edge cases

Every case, before and after

Each case is real codemod output. Both versions render in this page with Tailwind's own CSS, and every element's computed style is compared. A green dot means they render the same.

79cases
51moved to className
24stay, with a reason
...render the same
  • Dashed line: box edge
  • Hatch: padding
  • Tint: content

Cases that use theme tokens are checked against the token shown on their card, inside @theme { }. Class sizes use 16px per rem, as Uniwind does.

Press state

Pressable styles that read pressed or hovered. 3 cases.

Pressed array becomes active class

Moved to className.

BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  button: { padding: 16 },  pressed: { opacity: 0.5 },}); export const Button = () => <Pressable style={({ pressed }) => [styles.button, pressed && styles.pressed]} />;
AfterclassName
import { Pressable, View } from 'react-native'; export const Button = () => <Pressable className="p-4 active:opacity-50" />;
Checking

Pressed callback that reads hovered stays

Left as StyleSheet, on purpose.

stays: used outside style prop (3 styles)
BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  button: { padding: 16 },  pressed: { opacity: 0.5 },}); export const Button = () => <Pressable style={({ pressed, hovered }) => [styles.button, pressed && styles.pressed, hovered && styles.pressed]} />;
Afterfile unchanged
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  button: { padding: 16 },  pressed: { opacity: 0.5 },}); export const Button = () => <Pressable style={({ pressed, hovered }) => [styles.button, pressed && styles.pressed, hovered && styles.pressed]} />;
Checking

Pressed ternary becomes plain and active classes

Moved to className.

BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  idle: { opacity: 1 },  pressed: { opacity: 0.5 },}); export const Button = () => <Pressable style={({ pressed }) => (pressed ? styles.pressed : styles.idle)} />;
AfterclassName
import { Pressable, View } from 'react-native'; export const Button = () => <Pressable className="opacity-100 active:opacity-50" />;
Checking

Conditions and class merge

Ternaries, && parts and arrays, with and without a class merge function. 10 cases.

And member becomes class merge argument

Moved to className.

class merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  base: { padding: 16 },  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={[styles.base, isOn && styles.on]} />;
AfterclassName
import { View } from 'react-native';import { cn } from '@/lib/cn'; export const Box = ({ isOn }) => <View className={cn('p-4', isOn && 'opacity-50')} />;
Checking

Class merge import is not added twice

Moved to className.

class merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native';import { cn } from '@/lib/cn'; const styles = StyleSheet.create({  base: { padding: 16 },  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={[styles.base, isOn && styles.on]} />;
AfterclassName
import { View } from 'react-native';import { cn } from '@/lib/cn'; export const Box = ({ isOn }) => <View className={cn('p-4', isOn && 'opacity-50')} />;
Checking

Conditional without class merge stays

Moved to className.

stays: conditional without class merge
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  base: { padding: 16 },  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={[styles.base, isOn && styles.on]} />;
AfterclassName
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View className="p-4" style={isOn && styles.on} />;
Checking

File with its own cn name stays

Left as StyleSheet, on purpose.

stays: conditional without class mergeclass merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const cn = (flag) => flag; const styles = StyleSheet.create({  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={isOn ? styles.on : null} />;
Afterfile unchanged
import { View, StyleSheet } from 'react-native'; const cn = (flag) => flag; const styles = StyleSheet.create({  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={isOn ? styles.on : null} />;
Checking

Plain use needs no class merge

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View style={styles.box} />;
AfterclassName
import { View } from 'react-native'; export const Box = () => <View className="p-4" />;
Checking

Static array flattens

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  row: { flexDirection: "row" },  gap: { gap: 8 },}); export const Row = () => <View style={[styles.row, styles.gap]} />;
AfterclassName
import { View } from "react-native"; export const Row = () => <View className="flex-row gap-2" />;
Checking

Style const with condition uses class merge

Moved to className.

BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native';import { cn } from '@/lib/cn'; const styles = StyleSheet.create({  card: { padding: 16 },  active: { opacity: 0.5 },}); export const Card = ({ isActive }) => {  const cardStyle = [styles.card, isActive && styles.active];   return <View style={cardStyle} />;};
AfterclassName
import { Pressable, View } from 'react-native';import { cn } from '@/lib/cn'; export const Card = ({ isActive }) => {  const cardClassName = cn('p-4', isActive && 'opacity-50');   return <View className={cardClassName} />;};
Checking

Ternary becomes class merge ternary

Moved to className.

class merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  on: { opacity: 1 },  off: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={isOn ? styles.on : styles.off} />;
AfterclassName
import { View } from 'react-native';import { cn } from '@/lib/cn'; export const Box = ({ isOn }) => <View className={cn(isOn ? 'opacity-100' : 'opacity-50')} />;
Checking

Ternary with empty branch becomes class merge ternary

Moved to className.

class merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  on: { opacity: 0.5 },}); export const Box = ({ isOn }) => <View style={isOn ? styles.on : null} />;
AfterclassName
import { View } from 'react-native';import { cn } from '@/lib/cn'; export const Box = ({ isOn }) => <View className={cn(isOn ? 'opacity-50' : '')} />;
Checking

Two conditions become two class merge arguments

Moved to className.

class merge: cn
BeforeStyleSheet.create
import { View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  base: { padding: 16 },  on: { opacity: 0.5 },  big: { margin: 8 },}); export const Box = ({ isOn, isBig }) => <View style={[styles.base, isOn && styles.on, isBig && styles.big]} />;
AfterclassName
import { View } from 'react-native';import { cn } from '@/lib/cn'; export const Box = ({ isOn, isBig }) => <View className={cn('p-4', isOn && 'opacity-50', isBig && 'm-2')} />;
Checking

Components

Your own components, Reanimated, gradients and content container styles. 11 cases.

Color prop on host component stays

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { ActivityIndicator, StyleSheet } from "react-native"; const styles = StyleSheet.create({  spinner: { padding: 8 },}); export const Spinner = () => <ActivityIndicator color="#6d28d9" size="small" />;
Afterfile unchanged
import { ActivityIndicator, StyleSheet } from "react-native"; const styles = StyleSheet.create({  spinner: { padding: 8 },}); export const Spinner = () => <ActivityIndicator color="#6d28d9" size="small" />;
Checking

Comment above component is kept

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); // the main boxexport const Box = () => <View style={styles.box} />;
AfterclassName
import { View } from "react-native"; // the main boxexport const Box = () => <View className="p-4" />;
Checking

Content container style becomes class name

Moved to className.

BeforeStyleSheet.create
import { ScrollView, StyleSheet } from "react-native"; const styles = StyleSheet.create({  list: { padding: 16 },}); export const List = () => <ScrollView contentContainerStyle={styles.list} />;
AfterclassName
import { ScrollView } from "react-native"; export const List = () => <ScrollView contentContainerClassName="p-4" />;
Checking

Existing static class name gets classes added

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View className="flex-1" style={styles.box} />;
AfterclassName
import { View } from "react-native"; export const Box = () => <View className="flex-1 p-4" />;
Checking

Imported component ignoring class name stays

Left as StyleSheet, on purpose.

stays: component has no class name
BeforeStyleSheet.create
import { StyleSheet } from "react-native";import { Card } from "./card"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Card style={styles.box} />;
Afterfile unchanged
import { StyleSheet } from "react-native";import { Card } from "./card"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Card style={styles.box} />;
Checking

Imported component reading class name gets class name

Moved to className.

BeforeStyleSheet.create
import { StyleSheet } from "react-native";import { Card } from "./card"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Card style={styles.box} />;
AfterclassName
import { Card } from "./card"; export const Screen = () => <Card className="flex-1" />;
Checking

Imported component with base style stays

Left as StyleSheet, on purpose.

stays: component has no class name
BeforeStyleSheet.create
import { StyleSheet } from "react-native";import { Card } from "./card"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Card style={styles.box} />;
Afterfile unchanged
import { StyleSheet } from "react-native";import { Card } from "./card"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Card style={styles.box} />;
Checking

Linear gradient has no class name

Left as StyleSheet, on purpose.

stays: component has no class name
BeforeStyleSheet.create
import { StyleSheet } from "react-native";import { LinearGradient } from "expo-linear-gradient"; const styles = StyleSheet.create({  fill: { padding: 16 },}); export const Fill = () => <LinearGradient colors={["#000", "#fff"]} style={styles.fill} />;
Afterfile unchanged
import { StyleSheet } from "react-native";import { LinearGradient } from "expo-linear-gradient"; const styles = StyleSheet.create({  fill: { padding: 16 },}); export const Fill = () => <LinearGradient colors={["#000", "#fff"]} style={styles.fill} />;
Checking

Own component with class name prop gets class name

Moved to className.

BeforeStyleSheet.create
import { StyleSheet } from "react-native"; interface FancyProps {  className?: string;} const Fancy = (props: FancyProps) => null; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Fancy style={styles.box} />;
AfterclassName
interface FancyProps {  className?: string;} const Fancy = (props: FancyProps) => null; export const Screen = () => <Fancy className="flex-1" />;
Checking

Own component without class name prop stays

Left as StyleSheet, on purpose.

stays: component has no class name
BeforeStyleSheet.create
import { StyleSheet } from "react-native"; interface FancyProps {  title: string;} const Fancy = (props: FancyProps) => null; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Fancy title="a" style={styles.box} />;
Afterfile unchanged
import { StyleSheet } from "react-native"; interface FancyProps {  title: string;} const Fancy = (props: FancyProps) => null; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Screen = () => <Fancy title="a" style={styles.box} />;
Checking

Reanimated view gets class name

Moved to className.

BeforeStyleSheet.create
import { StyleSheet } from "react-native";import Animated from "react-native-reanimated"; const styles = StyleSheet.create({  box: { flex: 1 },}); export const Box = () => <Animated.View style={styles.box} />;
AfterclassName
import Animated from "react-native-reanimated"; export const Box = () => <Animated.View className="flex-1" />;
Checking

Style order and props

Caller styles, spread props and element shapes. 5 cases.

Caller style before sheet style flips order

Left as StyleSheet, on purpose.

stays: style order would flip
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  fixed: { padding: 16 },}); export const Box = (props) => <View style={[props.style, styles.fixed]} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  fixed: { padding: 16 },}); export const Box = (props) => <View style={[props.style, styles.fixed]} />;
Checking

Caller style still wins

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { padding: 16 },}); export const Card = ({ style }) => <View style={[styles.card, style]} />;
AfterclassName
import { View } from "react-native"; export const Card = ({ style }) => <View className="p-4" style={style} />;
Checking

Props after style are kept

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View style={styles.box} testID="box" />;
AfterclassName
import { View } from "react-native"; export const Box = () => <View className="p-4" testID="box" />;
Checking

Props before style are kept

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View testID="box" style={styles.box} />;
AfterclassName
import { View } from "react-native"; export const Box = () => <View testID="box" className="p-4" />;
Checking

Self closing and wrapping elements

Moved to className.

BeforeStyleSheet.create
import { View, Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  title: { fontSize: 18, lineHeight: 28 },}); export const Title = () => <Text style={styles.title}>hello</Text>;
AfterclassName
import { View, Text } from "react-native"; export const Title = () => <Text className="text-lg">hello</Text>;
Checking

Values to classes

Each style value and the class it becomes. 29 cases.

Absolute fill stays unchanged

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; export const Cover = () => <View style={StyleSheet.absoluteFill} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; export const Cover = () => <View style={StyleSheet.absoluteFill} />;
Checking

Absolute position with offsets

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  badge: { position: "absolute", top: 0, right: 0 },}); export const Badge = () => <View style={styles.badge} />;
AfterclassName
import { View } from "react-native"; export const Badge = () => <View className="absolute top-0 right-0" />;
Checking

Border width and color

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { borderWidth: 1, borderColor: "#000" },}); export const Box = () => <View style={styles.box} />;
AfterclassName
import { View } from "react-native"; export const Box = () => <View className="border border-black" />;
Checking

Card rule moves to classes

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { borderRadius: 12, backgroundColor: "#fff", padding: 16 },}); export const Card = () => <View style={styles.card} />;
AfterclassName
import { View } from "react-native"; export const Card = () => <View className="rounded-xl bg-white p-4" />;
Checking

Center content

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  center: { flex: 1, alignItems: "center", justifyContent: "center" },}); export const Center = () => <View style={styles.center} />;
AfterclassName
import { View } from "react-native"; export const Center = () => <View className="flex-1 items-center justify-center" />;
Checking

Color names become background and border classes

Moved to className.

@theme --color-surface: #ffffff; --color-line: #e2e8f0;
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  card: {    backgroundColor: colors.card,    borderColor: colors.border,    borderWidth: 1,  },}); export const Card = () => <View style={styles.card} />;
AfterclassName
import { View } from "react-native"; export const Card = () => <View className="bg-surface border-line border" />;
Checking

Elevation has no class

Left as StyleSheet, on purpose.

stays: no exact class
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { elevation: 4 },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { elevation: 4 },}); export const Box = () => <View style={styles.box} />;
Checking

Flex row with gap

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  row: { flexDirection: "row", gap: 8 },}); export const Row = () => <View style={styles.row} />;
AfterclassName
import { View } from "react-native"; export const Row = () => <View className="flex-row gap-2" />;
Checking

Flex wrap and align self

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  wrap: { flexWrap: "wrap", alignSelf: "flex-start" },}); export const Wrap = () => <View style={styles.wrap} />;
AfterclassName
import { View } from "react-native"; export const Wrap = () => <View className="flex-wrap self-start" />;
Checking

Font size 17 without arbitrary values has no class

Left as StyleSheet, on purpose.

stays: no exact class
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { fontSize: 17 },}); export const Label = () => <Text style={styles.label} />;
Afterfile unchanged
import { Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { fontSize: 17 },}); export const Label = () => <Text style={styles.label} />;
Checking

Font size alone is arbitrary

Moved to className.

arbitrary values on
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { fontSize: 18 },}); export const Label = () => <Text style={styles.label} />;
AfterclassName
import { Text } from "react-native"; export const Label = () => <Text className="text-[18px]" />;
Checking

Font size with line height is text size

Moved to className.

BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { fontSize: 18, lineHeight: 28 },}); export const Label = () => <Text style={styles.label} />;
AfterclassName
import { Text } from "react-native"; export const Label = () => <Text className="text-lg" />;
Checking

Hairline width stays dynamic

Left as StyleSheet, on purpose.

stays: dynamic value
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  line: { borderBottomWidth: StyleSheet.hairlineWidth },}); export const Line = () => <View style={styles.line} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  line: { borderBottomWidth: StyleSheet.hairlineWidth },}); export const Line = () => <View style={styles.line} />;
Checking

Left and right margins become logical

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { marginLeft: 8, paddingRight: 16 },}); export const Box = () => <View style={styles.box} />;
AfterclassName
import { View } from "react-native"; export const Box = () => <View className="ms-2 pe-4" />;
Checking

Margin and margin horizontal conflict

Left as StyleSheet, on purpose.

stays: family conflict
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { margin: 16, marginHorizontal: 8 },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { margin: 16, marginHorizontal: 8 },}); export const Box = () => <View style={styles.box} />;
Checking

Module constant width has no exact class

Left as StyleSheet, on purpose.

stays: no exact class
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const SIZE = getSize(); const styles = StyleSheet.create({  box: { width: SIZE },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const SIZE = getSize(); const styles = StyleSheet.create({  box: { width: SIZE },}); export const Box = () => <View style={styles.box} />;
Checking

Negative margin

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  pull: { marginTop: -8 },}); export const Pull = () => <View style={styles.pull} />;
AfterclassName
import { View } from "react-native"; export const Pull = () => <View className="-mt-2" />;
Checking

Opacity half

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  faded: { opacity: 0.5 },}); export const Faded = () => <View style={styles.faded} />;
AfterclassName
import { View } from "react-native"; export const Faded = () => <View className="opacity-50" />;
Checking

Overflow hidden

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  clip: { overflow: "hidden" },}); export const Clip = () => <View style={styles.clip} />;
AfterclassName
import { View } from "react-native"; export const Clip = () => <View className="overflow-hidden" />;
Checking

Padding and padding top conflict

Left as StyleSheet, on purpose.

stays: family conflict
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16, paddingTop: 8 },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16, paddingTop: 8 },}); export const Box = () => <View style={styles.box} />;
Checking

Padding left and right in one rule stay as family conflict

Left as StyleSheet, on purpose.

stays: family conflict
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  row: { paddingLeft: 4, paddingRight: 8 },}); export const Row = () => <View style={styles.row} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  row: { paddingLeft: 4, paddingRight: 8 },}); export const Row = () => <View style={styles.row} />;
Checking

Percent width

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  half: { width: "50%" },}); export const Half = () => <View style={styles.half} />;
AfterclassName
import { View } from "react-native"; export const Half = () => <View className="w-1/2" />;
Checking

Rounded full border radius

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  dot: { width: 8, height: 8, borderRadius: 9999 },}); export const Dot = () => <View style={styles.dot} />;
AfterclassName
import { View } from "react-native"; export const Dot = () => <View className="w-2 h-2 rounded-full" />;
Checking

Shadow offset has no class

Left as StyleSheet, on purpose.

stays: no exact class
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { shadowOffset: { width: 0, height: 2 } },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { shadowOffset: { width: 0, height: 2 } },}); export const Box = () => <View style={styles.box} />;
Checking

Split keeps dynamic width in style

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = ({ size }) => <View style={[styles.box, { width: size }]} />;
AfterclassName
import { View } from "react-native"; export const Box = ({ size }) => <View className="p-4" style={{ width: size }} />;
Checking

Text align center

Moved to className.

BeforeStyleSheet.create
import { View, Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { textAlign: "center" },}); export const Label = () => <Text style={styles.label} />;
AfterclassName
import { View, Text } from "react-native"; export const Label = () => <Text className="text-center" />;
Checking

Text style moves on text

Moved to className.

BeforeStyleSheet.create
import { View, Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  title: { fontWeight: "bold", color: "#000" },}); export const Title = () => <Text style={styles.title} />;
AfterclassName
import { View, Text } from "react-native"; export const Title = () => <Text className="font-bold text-black" />;
Checking

Theme color becomes token class

Moved to className.

@theme --color-brand: #6d28d9;
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native"; const styles = StyleSheet.create({  label: { color: "#6d28d9" },}); export const Label = () => <Text style={styles.label} />;
AfterclassName
import { Text } from "react-native"; export const Label = () => <Text className="text-brand" />;
Checking

Zero padding

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  flush: { padding: 0 },}); export const Flush = () => <View style={styles.flush} />;
AfterclassName
import { View } from "react-native"; export const Flush = () => <View className="p-0" />;
Checking

Files, sheets and style consts

Sheets shared across files, style constants and what stays. 21 cases.

Color name becomes theme class

Moved to className.

@theme --color-ink-soft: #64748b;
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  caption: { color: colors.mutedForeground },}); export const Caption = () => <Text style={styles.caption} />;
AfterclassName
import { Text } from "react-native"; export const Caption = () => <Text className="text-ink-soft" />;
Checking

Color name missing from theme stays

Left as StyleSheet, on purpose.

stays: color name not in theme@theme --color-ink: #0f172a;
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  caption: { color: colors.mutedForeground },}); export const Caption = () => <Text style={styles.caption} />;
Afterfile unchanged
import { Text, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  caption: { color: colors.mutedForeground },}); export const Caption = () => <Text style={styles.caption} />;
Checking

Create styles wrapper and hook are removed

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native";import { useTheme } from "./theme"; const createStyles = (theme) =>  StyleSheet.create({    box: { flex: 1 },  }); export const Screen = () => {  const theme = useTheme();  const styles = createStyles(theme);   return <View style={styles.box} />;};
AfterclassName
import { View } from "react-native"; export const Screen = () => {  return <View className="flex-1" />;};
Checking

Empty rule is unchanged

Left as StyleSheet, on purpose.

stays: dynamic value
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: {},}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: {},}); export const Box = () => <View style={styles.box} />;
Checking

Exported sheet is skipped

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; export const styles = StyleSheet.create({  card: { padding: 16 },}); export const Card = () => <View style={styles.card} />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; export const styles = StyleSheet.create({  card: { padding: 16 },}); export const Card = () => <View style={styles.card} />;
Checking

File without style sheet is unchanged

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
export const Screen = () => null;
Afterfile unchanged
export const Screen = () => null;
Checking

Flatten call keeps rule

Left as StyleSheet, on purpose.

stays: used outside style prop
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { padding: 16 },}); const flatCard = StyleSheet.flatten(styles.card); export const size = flatCard;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { padding: 16 },}); const flatCard = StyleSheet.flatten(styles.card); export const size = flatCard;
Checking

Inline style object without sheet stays unchanged

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { View } from "react-native"; export const Gap = () => <View style={{ marginTop: 8 }} />;
Afterfile unchanged
import { View } from "react-native"; export const Gap = () => <View style={{ marginTop: 8 }} />;
Checking

Nested color name becomes theme class

Moved to className.

@theme --color-brand: #6d28d9;
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native";import { palette } from "@/theme/palette"; const styles = StyleSheet.create({  label: { color: palette.brand.main },}); export const Label = () => <Text style={styles.label} />;
AfterclassName
import { Text } from "react-native"; export const Label = () => <Text className="text-brand" />;
Checking

One rule used twice moves both

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Pair = () => (  <View>    <View style={styles.box} />    <View style={styles.box} />  </View>);
AfterclassName
import { View } from "react-native"; export const Pair = () => (  <View>    <View className="p-4" />    <View className="p-4" />  </View>);
Checking

Platform select stays

Left as StyleSheet, on purpose.

stays: dynamic value
BeforeStyleSheet.create
import { View, Platform, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { ...Platform.select({ ios: { padding: 8 }, android: { padding: 4 } }) },}); export const Box = () => <View style={styles.box} />;
Afterfile unchanged
import { View, Platform, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { ...Platform.select({ ios: { padding: 8 }, android: { padding: 4 } }) },}); export const Box = () => <View style={styles.box} />;
Checking

Scheme wrapper stays unchanged

Left as StyleSheet, on purpose.

stays: scheme wrapper
BeforeStyleSheet.create
import { Text, View } from "react-native";import { createSchemeStyleSets } from "@/theme/scheme-styles"; const schemeStyleSets = createSchemeStyleSets((colors) => ({  card: {    flex: 1,    padding: 16,    backgroundColor: colors.surface,  },})); export const Card = () => {  const styles = useSchemeStyles(schemeStyleSets);   return (    <View style={styles.card}>      <Text>Hello</Text>    </View>  );};
Afterfile unchanged
import { Text, View } from "react-native";import { createSchemeStyleSets } from "@/theme/scheme-styles"; const schemeStyleSets = createSchemeStyleSets((colors) => ({  card: {    flex: 1,    padding: 16,    backgroundColor: colors.surface,  },})); export const Card = () => {  const styles = useSchemeStyles(schemeStyleSets);   return (    <View style={styles.card}>      <Text>Hello</Text>    </View>  );};
Checking

Sheet with no uses is unchanged

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View />;
Afterfile unchanged
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); export const Box = () => <View />;
Checking

Style const passed to function stays

Left as StyleSheet, on purpose.

stays: used outside style prop
BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  card: { padding: 16 },}); const cardStyle = styles.card; export const Card = () => <View style={cardStyle} accessibilityHint={String(cardStyle)} />;
Afterfile unchanged
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  card: { padding: 16 },}); const cardStyle = styles.card; export const Card = () => <View style={cardStyle} accessibilityHint={String(cardStyle)} />;
Checking

Style const used only as style becomes class name const

Moved to className.

BeforeStyleSheet.create
import { Pressable, View, StyleSheet } from 'react-native'; const styles = StyleSheet.create({  card: { padding: 16 },}); const cardStyle = styles.card; export const Card = () => <View style={cardStyle} />;
AfterclassName
import { Pressable, View } from 'react-native'; const cardClassName = "p-4"; export const Card = () => <View className={cardClassName} />;
Checking

Style property read in code keeps rule

Moved to className.

stays: used outside style prop
BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { padding: 16 },}); const cardPadding = styles.card.padding; export const Card = () => <View style={styles.card} />;
AfterclassName
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  card: { padding: 16 },}); const cardPadding = styles.card.padding; export const Card = () => <View className="p-4" />;
Checking

Style without sheet reference unchanged

Left as StyleSheet, on purpose.

BeforeStyleSheet.create
import { View } from "react-native"; export const Box = (props) => <View style={props.style} />;
Afterfile unchanged
import { View } from "react-native"; export const Box = (props) => <View style={props.style} />;
Checking

Two rules two elements

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  outer: { padding: 16 },  inner: { margin: 8 },}); export const Nested = () => (  <View style={styles.outer}>    <View style={styles.inner} />  </View>);
AfterclassName
import { View } from "react-native"; export const Nested = () => (  <View className="p-4">    <View className="m-2" />  </View>);
Checking

Unmapped color stays while mapped color moves

Moved to className.

stays: no exact class@theme --color-ink: #0f172a;
BeforeStyleSheet.create
import { Text, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  title: { color: colors.foreground },  warning: { color: colors.warning },}); export const Title = () => <Text style={styles.title} />;export const Warning = () => <Text style={styles.warning} />;
AfterclassName
import { Text, StyleSheet } from "react-native";import { colors } from "@/theme/colors"; const styles = StyleSheet.create({  warning: { color: colors.warning },}); export const Title = () => <Text className="text-ink" />;export const Warning = () => <Text style={styles.warning} />;
Checking

Untouched lines keep their spacing

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },}); const    spaced   =   1; export const Box = () => <View style={styles.box} />;
AfterclassName
import { View } from "react-native"; const    spaced   =   1; export const Box = () => <View className="p-4" />;
Checking

Unused sheet rule is kept when one rule moves

Moved to className.

BeforeStyleSheet.create
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  box: { padding: 16 },  unused: { margin: 8 },}); export const Box = () => <View style={styles.box} />;
AfterclassName
import { View, StyleSheet } from "react-native"; const styles = StyleSheet.create({  unused: { margin: 8 },}); export const Box = () => <View className="p-4" />;
Checking