UNPKG

twrnc

Version:
511 lines (409 loc) 17.3 kB
# Tailwind React Native Classnames 🏄‍♂️ > A simple, expressive API for TailwindCSS + React Native, written in TypeScript ```jsx import { View, Text } from 'react-native'; import tw from 'twrnc'; const MyComponent = () => ( <View style={tw`p-4 android:pt-2 bg-white dark:bg-black`}> <Text style={tw`text-md text-black dark:text-white`}>Hello World</Text> </View> ); ``` ## Features 🚀 - full support for all _native_ RN styles with tailwind counterparts: ([view](https://reactnative.dev/docs/view-style-props), [layout](https://reactnative.dev/docs/layout-props), [image](https://reactnative.dev/docs/image-style-props), [shadow](https://reactnative.dev/docs/shadow-props), and [text](https://reactnative.dev/docs/text-style-props)). - very fast: best performance of all RN styling libraries, according to [this benchmark](https://github.com/efstathiosntonas/react-native-style-libraries-benchmark). - compatible with Tailwind CSS v3 and v2 - respects your `tailwind.config.js` for full configuration - platform prefixes: `android:mt-4 ios:mt-2` - dark mode support: `bg-white dark:bg-black` - media query support: `w-48 lg:w-64` (also, arbitrary: `min-w-[600px]:flex-wrap`) - device orientation prefixes: `portrait:flex-col landscape:flex-row` - `vw` and `vh` unit support: `h-screen`, `min-w-screen`, `w-[25vw]`, etc... - `retina` device pixel density prefix: `w-4 retina:w-2` - arbitrary, JIT-style classes: `mt-[31px] bg-[#eaeaea] text-red-200/75`, etc... - tagged template literal syntax for most common usage - merges supplied RN style objects for unsupported utilities or complex use cases - supports custom utility creation via standard [plugin config](https://tailwindcss.com/docs/adding-new-utilities#using-a-plugin). - flexible, conditional styles based on [classnames package api](https://github.com/JedWatson/classnames). - written 100% in TypeScript, ships with types ## Docs: - [Installation](#installation) - [API](#api) - [Customization](#customization) - [Enabling Device-Context Prefixes](#enabling-device-context-prefixes) - [Taking Control of Dark Mode](#taking-control-of-dark-mode) - [Customizing Breakpoints](#customizing-breakpoints) - [Adding Custom Classes](#adding-custom-classes) - [Matching Conditional Prefixes](#matching-conditional-prefixes) - [Box-Shadows](#box-shadows) - [RN-Only Additions](#rn-only-additions) - [JIT-style Arbitrary Values](#jit-style-arbitrary-values) - [VS Code Intellisense](#vs-code-intellisense) - [JetBrains IDEs Intellisense](#jetbrains-ides-intellisense) - [Memo-Busting](#memo-busting) - [Migrating from previous versions](#migrating-from-previous-versions) - [Prior Art](#prior-art) ## Installation ```bash npm install twrnc ``` ## API The default export is an ES6 _Tagged template function_ which is nice and terse for the most common use case -- passing a bunch of space-separated Tailwind classes and getting back a react-native style object: ```js import tw from 'twrnc'; tw`pt-6 bg-blue-100`; // -> { paddingTop: 24, backgroundColor: 'rgba(219, 234, 254, 1)' } ``` In the spirit of Tailwindcss's intuitive responsive prefix syntax, `twrnc` adds support for **platform prefixes** to conditionally apply styles based on the current platform: ```js // 😎 styles only added if platform matches tw`ios:pt-4 android:pt-2`; ``` Media query-like breakpoint prefixes supported (see [Breakpoints](#customizing-breakpoints) for configuration): ```js // 😎 faux media queries tw`flex-col lg:flex-row`; ``` Dark mode support (see [here](#enabling-device-context-prefixes) for configuration); ```js // 😎 dark mode support tw`bg-white dark:bg-black`; ``` You can also use `tw.style()` for handling more complex class name declarations. The api for this function is directly taken from the excellent [classnames](https://github.com/JedWatson/classnames#readme) package. ```js // pass multiple args tw.style('text-sm', 'bg-blue-100', 'flex-row mb-2'); // arrays of classnames work too tw.style(['text-sm', 'bg-blue-100']); // falsy stuff is ignored, so you can do conditionals like this tw.style(isOpen && 'bg-blue-100'); // { [className]: boolean } style - key class only added if value is `true` tw.style({ 'bg-blue-100': isActive, 'text-red-500': invalid, }); // or, combine tailwind classes with plain react-native style object: tw.style('bg-blue-100', { resizeMode: `repeat` }); // mix and match input styles as much as you want tw.style('bg-blue-100', ['flex-row'], { 'text-xs': true }, { fontSize: 9 }); ``` If you need some styling that is not supported in a utility class, or just want to do some custom run-time logic, you can **pass raw RN style objects** to `tw.style()`, and they get merged in with the styles generated from any other utility classes: ```js tw.style(`mt-1`, { resizeMode: `repeat`, width: `${progress}%`, }); // -> { marginTop: 4, resizeMode: 'repeat', width: '32%' } ``` The `tw` function also has a method `color` that can be used to get back a string value of a tailwind color. Especially useful if you're using a customized color palette. ```js tw.color('blue-100'); // `bg|text|border-blue-100` also work // -> "rgba(219, 234, 254, 1)" ``` You can import the main `tw` function and reach for `tw.style` only when you need it: ```jsx import tw from 'twrnc'; const MyComponent = () => ( <View style={tw`bg-blue-100`}> <Text style={tw.style('text-md', invalid && 'text-red-500')}>Hello</Text> </View> ); ``` ...or if the tagged template function isn't your cup of tea, just import `tw.style` as `tw`: ```jsx import { style as tw } from 'twrnc'; const MyComponent = () => ( <View style={tw('bg-blue-100', invalid && 'text-red-500')}></View> ); ``` ## Customization You can use `twrnc` right out of the box if you haven't customized your `tailwind.config.js` file at all. But more likely you've got some important app-specific tailwind customizations you'd like to use. For that reason, we expose the ability to create a **custom configured version** of the `tw` function object. ```js // lib/tailwind.js import { create } from 'twrnc'; // create the customized version... const tw = create(require(`../../tailwind.config.js`)); // <- your path may differ // ... and then this becomes the main function your app uses export default tw; ``` ...and in your component files import your own customized version of the function instead: ```jsx // SomeComponent.js import tw from './lib/tailwind'; ``` <!-- prettier-ignore-start --> > [!note] > If you are using `export default {}` in your `tailwind.config.js` file, you need > to pass the `default` to `create` util from your config require: > `require('../../tailwind.config.js').default`. <!-- prettier-ignore-end --> ## Enabling Device-Context Prefixes To enable prefixes that require runtime device data, like _dark mode_, and _screen size breakpoints_, etc., you need to connect the `tw` function with a dynamic source of device context information. The library exports a React hook called `useDeviceContext` that takes care of this for you. It should be included **one time**, at the _root of your component hierarchy,_ as shown below: ```jsx import tw from './lib/tailwind'; // or, if no custom config: `from 'twrnc'` import { useDeviceContext } from 'twrnc'; export default function App() { useDeviceContext(tw); // <- 👋 return ( <View style={tw`bg-white dark:bg-black`}> <Text style={tw`text-black dark:text-white`}>Hello</Text> </View> ); } ``` > ⚠️ If you're using Expo, make sure to make the following change in `app.json` to use the > `dark:` prefix as Expo by default locks your app to light mode only. ```json { "expo": { "userInterfaceStyle": "automatic" } } ``` ## Taking Control of Dark Mode By default, if you use `useDeviceContext()` as outlined above, your app will respond to ambient changes in the _device's color scheme_ (set in system preferences). If you'd prefer to **explicitly control** the color scheme of your app with some in-app mechanism, you'll need to configure things slightly differently: ```jsx import { useDeviceContext, useAppColorScheme } from 'twrnc'; export default function App() { useDeviceContext(tw, { // 1️⃣ opt OUT of listening to DEVICE color scheme events observeDeviceColorSchemeChanges: false, // 2️⃣ and supply an initial color scheme initialColorScheme: `light`, // 'light' | 'dark' | 'device' }); // 3️⃣ use the `useAppColorScheme` hook anywhere to get a reference to the current // colorscheme, with functions to modify it (triggering re-renders) when you need const [colorScheme, toggleColorScheme, setColorScheme] = useAppColorScheme(tw); return ( // 4️⃣ use one of the setter functions, like `toggleColorScheme` in your app <TouchableOpacity onPress={toggleColorScheme}> <Text style={tw`text-black dark:text-white`}>Switch Color Scheme</Text> </TouchableOpacity> ); } ``` ## Customizing Breakpoints You can **customize the breakpoints** in the same way as a [tailwindcss web project](https://v3.tailwindcss.com/docs/breakpoints), using `tailwind.config.js`. The defaults that ship with `tailwindcss` are geared towards the web, so you likely want to set your own for device sizes you're interested in, like this: ```js // tailwind.config.js module.exports = { theme: { screens: { sm: '380px', md: '420px', lg: '680px', // or maybe name them after devices for `tablet:flex-row` tablet: '1024px', }, }, }; ``` ## Adding Custom Classes To add custom utilities, use the [plugin method](https://v3.tailwindcss.com/docs/adding-new-utilities#using-a-plugin) described in the tailwind docs, instead of writing to a `.css` file. ```js const plugin = require('tailwindcss/plugin'); module.exports = { plugins: [ plugin(({ addUtilities }) => { addUtilities({ '.btn': { padding: 3, borderRadius: 10, textTransform: `uppercase`, backgroundColor: `#333`, }, '.resize-repeat': { resizeMode: `repeat`, }, }); }), ], }; ``` Wil also allow you to supply a **string** of other utility classes (similar to `@apply`), instead of using **CSS-in-JS** style objects: ```js module.exports = { plugins: [ plugin(({ addUtilities }) => { addUtilities({ // 😎 similar to `@apply` '.btn': `px-4 py-1 rounded-full bg-red-800 text-white`, '.body-text': `font-serif leading-relaxed tracking-wide text-gray-800`, }); }), ], }; ``` ## Matching Conditional Prefixes `twrnc` also exposes a `tw.prefixMatch(...prefixes: string[]) => boolean` function that allows you to test whether a given prefix (or combination of prefixes) would produce a style given the current device context. This can be useful when you need to pass some primitive value to a component, and wish you could leverage `tw`'s knowledge of the current device, or really anywhere you just need to do some logic based on the device context. This could also be accomplished by importing `Platform` or a combination of other RN hooks, but chances are you've already imported your `tw` function, and this saves you re-implementing all that logic on your own: ```tsx const SomeComponent = () => ( <View> <Thumbnail imageSize={tw.prefixMatch(`portrait`) ? 60 : 90} /> {tw.prefixMatch(`ios`, `dark`) ? <CustomIosDarkModeThing /> : <Thing />} </View> ); ``` ## Box Shadows Box shadows [in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow#syntax) differ substantially from [shadow in RN](https://reactnative.dev/docs/shadow-props), so this library doesn't attempt to parse CSS box-shadow strings and translate them into RN style objects. Instead, it offers a number of low-level utilities not present in `tailwindcss`, which map to the [4 shadow props](https://reactnative.dev/docs/shadow-props) in RN: ```js // RN `shadowColor` tw`shadow-white`; // > { shadowColor: `#fff` } tw`shadow-red-200`; // > { shadowColor: `#fff` } tw`shadow-[#eaeaea]`; // > { shadowColor: `#eaeaea` } tw`shadow-black shadow-opacity-50`; // > { shadowColor: `rgba(0,0,0,0.5)` } // RN `shadowOffset` tw`shadow-offset-1`; // > { shadowOffset: { width: 4, height: 4 } } tw`shadow-offset-2/3`; // > { shadowOffset: { width: 8, height: 12 } } tw`shadow-offset-[3px]`; // > { shadowOffset: { width: 3, height: 3 } }], tw`shadow-offset-[4px]/[5px]`; // > { shadowOffset: { width: 4, height: 5 } }], // RN `shadowOpacity` tw`shadow-opacity-50`; // { shadowOpacity: 0.5 } // RN `shadowRadius` tw`shadow-radius-1`; // { shadowRadius: 4 } tw`shadow-radius-[10px]`; // { shadowRadius: 10 } ``` We also provide a _default implementation_ of the `shadow-<X>` utils [provided by tailwindcss](https://v3.tailwindcss.com/docs/box-shadow), so you can use: ```js tw`shadow-md`; /* -> { shadowOffset: { width: 1, height: 1 }, shadowColor: `#000`, shadowRadius: 3, shadowOpacity: 0.125, elevation: 3, } */ ``` To override the default implementations of these named shadow classes, [add your own custom utilities](#adding-custom-classes) -- any custom utilities you provide with the same names will override the ones this library ships with. ## RN-Only Additions `twrnc` implements all of the tailwind utilities which overlap with supported RN (native, not web) style props. But it also adds a sprinkling of RN-only utilities which don't map to web-css, including: - [low-level shadow utilities](#box-shadows) - [elevation](https://reactnative.dev/docs/view-style-props#elevation-android) (Android only), eg: `elevation-1`, `elevation-4` - `small-caps` -> `{fontVariant: 'small-caps'}` - number based font-weight utilities `font-100`, `font-400`, (100...900) - `direction-(inherit|ltr|rtl)` - `align-self: baseline;` via `self-baseline` - `include-font-padding` and `remove-font-padding` (Android only: `includeFontPadding`) - image tint color control (`tint-{color}` e.g. `tint-red-200`) - `pointer-events-(box-only|box-none)` ## JIT-Style Arbitrary Values Many of the arbitrary-style utilities made possible by Tailwind JIT are implemented in `twrnc`, including: - arbitrary colors: `bg-[#f0f]`, `text-[rgb(33,45,55)]` - negative values: `-mt-4`, `-tracking-[2px]` - shorthand color opacity: `text-red-200/75` (`red-200` at `75%` opacity) - merging color/opacity: `border-black border-opacity-75` - arbitrary opacity amounts: `opacity-73` - custom spacing: `mt-[4px]`, `-pb-[3px]`, `tracking-[2px]` - arbitrary fractional insets: `bottom-7/9`, `left-5/8` - arbitrary min/max width/height: `min-w-[40%]`, `max-h-3/8`, `w-[25vw]`, `h-[21px]` - arbitrary breakpoints: `min-w-[600px]:flex-row`, `max-h-[1200px]:p-4` Not every utility currently supports all variations of arbitrary values, so if you come across one you feel is missing, open an issue or a PR. ## VS Code Intellisense Add the following to the settings of the [official Tailwind plugin](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) for VS Code. ```jsonc // ... "tailwindCSS.classAttributes": [ // ... "style" ], "tailwindCSS.classFunctions": ["tw", "tw.color", "tw.style"], ``` More detailed instructions, including how to add snippets, are available [here](https://github.com/jaredh159/tailwind-react-native-classnames/discussions/124). ## JetBrains IDEs Intellisense For JetBrains IDEs (WebStorm, IntelliJ, etc.) Go to Settings | Languages & Frameworks | Style Sheets | Tailwind CSS. Add the following configuration options: ```jsonc // ... "classAttributes": [ // ... "style" ], "classFunctions": ["tw", "tw.color", "tw.style"], ``` It is important that you have a `tailwind.config.js` in the root of the repository even if the content is just `export default {}`, otherwise the Tailwind LSP won't start correctly. ## Memo Busting If you're using device-context prefixes (like `dark:`, and `md:`), _memoized_ components can cause problems by preventing re-renders when the color scheme or window size changes. You may not be memoizing explicitly yourself as many third-party libraries (like `react-navigation`) memoizes their own components. In order to help with this problem, `twrnc` exposes a `.memoBuster` property on the `tw` object. This string property is meant to passed as a `key` prop to break memoization boundaries. It is stable (preventing re-renders) until something in the device context changes, at which point it deterministically updates: ```tsx <SomeMemoizedComponent key={tw.memoBuster} /> ``` > This is not a perfect solution for **all** memoization issues. For caveats and more > context, see > [#112](https://github.com/jaredh159/tailwind-react-native-classnames/issues/112). ## Migrating from Previous Versions See [migration-guide.md](/migration-guide.md). ## Prior Art - The first version of this package (before it was re-written from scratch) was based heavily on the excellent [vadimdemedes/tailwind-rn](https://github.com/vadimdemedes/tailwind-rn). - The flexible `tw.style()` api was taken outright from [classnames](https://github.com/JedWatson/classnames#readme) - [TailwindCSS](https://tailwindcss.com) - [Tailwind JIT](https://tailwindcss.com/docs/just-in-time-mode) - [React Native](https://reactnative.dev)