AccueilClientsExpertisesOpen SourceBlogContactEstimer

16 septembre 2025

Stylisez votre application React Native avec Unistyles

9 minutes de lecture

Stylisez votre application React Native avec Unistyles
🇺🇸 This post is also available in english

Alors que la nouvelle architecture de React Native est maintenant dans un état stable, la quête à la performance elle n'est jamais terminée. Dans ce cadre, utiliser LA librairie de style pour notre application est n'est pas toujours évident. En effet celle-ci doit allier performance, flexibilité et simplicité d'utilisation. Nous avons exploré dans un précédent article Tamagui qui offre une certaine simplicité d'utilisation, mais qui est assez fastidieux à configurer lorsque l'on souhaite obtenir un rendu plus personnalisé.

Plus récemment, NativeWind a prit aussi beaucoup en popularité, profitant d'une certaine standardisation de l'utilisation de Tailwind côté web. Simple à configurer, avec une API quasi identique à celle de Tailwind, il est assez simple pour un développeur Web de se lancer dans la création d'une application React Native avec NativeWind. Néanmoins, son fonctionnement interne n'est pas toujours évident à comprendre et demande parfois d'être assez verbeux quand il s'agit de créer des composants très génériques (via l'utilisation de remapProps ou cssInterop par exemple).

C'est dans ce contexte de course à la performance et à la meilleure DX qu'Unistyles a sorti sa version 3, dont nous allons dans cet article explorer les fonctionnalités et les avantages de cette librairie.

Comment ça marche ?

Unistyles a pour vocation de fournir une API très proche de l'API StyleSheet de React Native, tout en y ajoutant des fonctionnalités supplémentaires comme la gestion de thème, le support des breakpoints, l'ajout de variants. Pour aller plus loin, Unistyles ajoute un aspect performance en gérant les styles directement au sein de la partie C++ de l'application. Concrètement, cela permet d'associer un style à un noeud natif de notre arbre de composants, et de faire en sorte que ce noeud réagisse aux changements de style qui peuvent se produire (changement de thème, propriété dépendante du viewport, etc.), le tout sans provoquer de re-rendu de notre arbre de composants côté JavaScript. Toutes ces optimisations sont orchestrées par un plugin Babel.

Utilisation

Explorons Unistyles en créant un projet Expo. Nous utiliserons le template "Blank" :

bun create expo-app demo-unistyles -t
cd demo-unistyles
npx expo install react-native-unistyles react-native-nitro-modules react-native-reanimated react-native-edge-to-edge

Ajoutons le plugin Babel :

npx expo customize babel.config.js
babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "react-native-unistyles/plugin",
        {
          root: "src", // Dossier racine contenant les composants stylisés
        },
      ],
    ],
  };
};

Créons maintenant un style très simple dans le composant App :

src/app.tsx
import { StyleSheet } from 'react-native-unistyles';
import { View } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
  },
});

const App = () => {
  return <View style={styles.container} />
}

export default App;

On constate finalement que, en l'état, l'API de Unistyles offre une parité 1:1 avec l'API StyleSheet de React Native, rendant ainsi une migration vers Unistyles très simple.

Theming

Nous allons maintenant utiliser ce qui est un atout d'Unistyles par rapport à StyleSheet : la possibilité de définir des thèmes.

Créons un fichier src/themes.ts :

src/themes.ts
import { StyleSheet } from "react-native-unistyles";

// Quelques utilitaires
const spacing = (size: number) => size * 4;
const radius = (size: number) => size * 4;

const utils = {
  spacing,
  radius,
};

export const light = {
  colors: {
    primary: "#007AFF",
    red: "#ed3e3e",
    background: "#ffffff",
  },
  utils,
};

export const dark = {
  colors: {
    primary: "#4da6ff",
    red: "#fa645c",
    background: "#3E3E3E",
  },
  utils,
};

StyleSheet.configure({
  themes: {
    light,
    dark,
  },
  settings: {
    /**
     * L'utilisation de thèmes nommés "dark" et "light" permet de les utiliser en fonction
     * du thème utilisé par le système d'exploitation. Pour cela, il faut s'assurer que
     * la propriété `userInterfaceStyle` de la configuration Expo soit définie en "automatic".
     */
    adaptiveThemes: true,
  },
});

type AppThemes = {
  light: typeof light;
  dark: typeof dark;
};

declare module "react-native-unistyles" {
  export interface UnistylesThemes extends AppThemes {}
}

Nous avons ici défini 2 thèmes de couleur différent, chacun destiné à s'adapter selon le thème du système d'exploitation. Il est évidemment possible de définir d'autres thèmes avec des noms différents. Pour que ces thèmes soient bien pris en compte avant le premier rendu, il est impératif d'importer ce fichier dans notre index.ts :

index.ts
import { registerRootComponent } from "expo";
import "./src/theme";
import App from "./src/app";

registerRootComponent(App);

Faisons maintenant usage de ces thèmes. Pour ce faire, il va falloir transformer légèrement notre fonction StyleSheet.create. Celle-ci prendra maintenant en paramètre une fonction recevant 2 arguments : le thème actuel et des variables de styling liées à l'appareil (nous aborderons cela plus tard).

src/app.tsx
import { StyleSheet } from 'react-native-unistyles';
import { View } from 'react-native';

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: theme.colors.background,
    padding: theme.utils.spacing(4)
  },
}));

const App = () => {
  return <View style={styles.container} />
}

export default App;

Si nous alternons entre le light et dark mode sur notre appareil, nous verrons la couleur de fond changer, le tout sans que notre arbre de composants React ne soit re-rendu !

Responsive

Il est possible de définir des styles selon la taille ou l'orientation de l'appareil. Par défaut, Unistyles fournit la possibilité de fournir des styles selon des breakpoints liés à l'orientation, mais il est tout à fait possible de définir nos propres breakpoints liés à la taille de l'écran. Essayons par exemple de changer la couleur de fond en fonction de l'orientation :

src/app.tsx
import { StyleSheet } from 'react-native-unistyles';
import { View } from 'react-native';

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: {
      portrait: theme.colors.background,
      landscape: theme.colors.primary,
    },
    padding: theme.utils.spacing(4)
  },
}));

const App = () => {
  return <View style={styles.container} />
}

export default App;

Maintenant, lorsque nous changeons l'orientation de notre appareil, la couleur de fond change, toujours sans provoquer de re-rendu !

Runtime Unistyles

Évoquons maintenant le sujet du runtime que l'on a vu précédemment. Unistyles expose des variables de runtime, qui sont des valeurs purement liées à l'appareil et qui sont accessibles à tout moment. Il existe 2 types de runtime :

  • le runtime global, exposant des variables accessibles à tout moment, mais aussi des fonctions permettant d'altérer certains aspects comme le thème utilisé, le contenu du thème, la couleur de fond de notre vue native, etc.
  • le runtime de style, appelé mini runtime, qui lui aussi expose des variables similaires à celles du runtime global, mais qui ont pour but d'être utilisées dans les styles. Ainsi, nos styles seront réactifs aux changements de valeurs de ces variables.

Faisons usage du mini runtime pour ajouter un espacement en haut de notre vue en fonction de la safe area :

src/app.tsx
import { StyleSheet } from 'react-native-unistyles';
import { View } from 'react-native';

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: {
      portrait: theme.colors.background,
      landscape: theme.colors.primary,
    },
    padding: theme.utils.spacing(4),
    paddingTop: rt.insets.top,
  },
}));

const App = () => {
  return <View style={styles.container} />
}

export default App;

Variants

À la manière de Tamagui et de bien d'autres librairies de style, Unistyles permet d'ajouter des variants pour un style précis. Un cas d'utilisation parfait pour cela est la création d'un bouton. Pour un aspect plus joli, nous allons même nous permettre d'animer la couleur du bouton quand un changement de couleur est détecté.

src/button.tsx
import { PropsWithChildren } from "react";
import {
  Pressable,
  type PressableProps,
  StyleProp,
  Text,
  ViewStyle,
} from "react-native";
import Animated, {
  useAnimatedStyle,
  withTiming,
} from "react-native-reanimated";
import { StyleSheet, type UnistylesVariants } from "react-native-unistyles";
import { useAnimatedVariantColor } from "react-native-unistyles/reanimated";

type Props = UnistylesVariants<typeof styles> &
  Omit<PressableProps, "style"> & {
    label: string;
    style?: StyleProp<ViewStyle>;
  };

const Button = ({
  type = "primary",
  style,
  label,
  children,
  ...props
}: Props) => {
  // Activation du variant pour le composant Button
  styles.useVariants({ type });
  // Récupération de la couleur de fond sous forme de shared value
  const color = useAnimatedVariantColor(styles.container, "backgroundColor");
  // Animation de la couleur de fond
  const animatedStyle = useAnimatedStyle(() => {
    return {
      backgroundColor: withTiming(color.value, { duration: 500 }),
    };
  });

  return (
    <Pressable {...props}>
      <Animated.View style={[styles.container, animatedStyle, style]}>
        <ButtonText type={type}>{label}</ButtonText>
      </Animated.View>
    </Pressable>
  );
};

const ButtonText = ({
  children,
  type,
}: PropsWithChildren<UnistylesVariants<typeof styles>>) => {
  // Activation du variant pour le composant ButtonText
  styles.useVariants({ type });

  return <Text style={styles.text}>{children}</Text>;
};

export default Button;

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    borderRadius: theme.utils.radius(2),
    flexDirection: "row",
    paddingHorizontal: theme.utils.spacing(4),
    paddingVertical: theme.utils.spacing(3),

    variants: {
      type: {
        primary: {
          backgroundColor: theme.colors.primary,
        },
        destructive: {
          backgroundColor: theme.colors.red,
        },
      },
    },
  },
  text: {
    fontSize: 14,
    variants: {
      type: {
        primary: {
          color: "white",
        },
        destructive: {
          color: "white",
        },
      },
    },
  },
}));

Nous avons défini ici 2 variants pour nos boutons. Ces variants peuvent être passé en props de notre composant Button.

src/app.tsx
import { StyleSheet } from 'react-native-unistyles';
import { View } from 'react-native';
import Button from './button';

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: {
      portrait: theme.colors.background,
      landscape: theme.colors.primary,
    },
    padding: theme.utils.spacing(4),
    paddingTop: rt.insets.top,
    gap: theme.utils.spacing(4),
  },
}));

const App = () => {
  return (
    <View style={styles.container}>
      <Button type="primary" label="Primary Button" />
      <Button type="destructive" label="Destructive Button" />
    </View>
  )
}

export default App;

Profitons-en pour faire usage du runtime global. Nous allons faire en sorte que nos boutons changent le thème de couleur utilisé.

src/app.tsx
import { StyleSheet, UnistylesRuntime } from 'react-native-unistyles';
import { View } from 'react-native';
import Button from './button';

const styles = StyleSheet.create((theme, rt) => ({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: {
      portrait: theme.colors.background,
      landscape: theme.colors.primary,
    },
    padding: theme.utils.spacing(4),
    paddingTop: rt.insets.top,
    gap: theme.utils.spacing(4),
  },
}));

const App = () => {
  return (
    <View style={styles.container}>
      <Button type="primary" label="Light mode" onPress={() => UnistylesRuntime.setTheme('light')} />
      <Button type="destructive" label="Dark mode" onPress={() => UnistylesRuntime.setTheme('dark')} />
    </View>
  )
}

export default App;

Maintenant au clic sur les boutons, leur couleur de fond change, encore une fois sans provoquer de re-rendu.

En plus des variants, Unistyles offre la possibilité d'ajouter des styles selon les valeurs de plusieurs variants : les variants composés.

Conclusion

Unistyles est une alternative très intéressante aux solutions existantes, de part son aspect performance, sa simplicité d'utilisation et ses similitudes avec l'API StyleSheet. À noter qu'un support Web est lui aussi disponible. Que choisir entre Unistyles et Tamagui ? Le choix est discutable mais de part sa facilité de configuration, Unistyles me semble être un bon choix. Si toutefois vous êtes un grand fan de Tailwind, le créateur d'Unistyles a créé Uniwind (pas encore sorti à l'heure de l'écriture de cet article), ayant pour but de combiner les performances de Unistyles avec les avantages de Tailwind.

Des idées de projet en React Native, une envie de migrer votre application vers Unistyles ? N'hésitez pas à nous contacter !

À découvrir également

Premier Octet vous accompagne dans le développement de vos projets avec react

En savoir plusNous contacter
18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub

X

Flux RSS

Bluesky

Naviguez à vue