Styling React Native with CSS

CSS loves React

The Gutenberg project started as a way to bring a new editor to WordPress, but not only to your admin dashboards. For the past two years, we have been working on a mobile version that leverages React Native to bring that same new editor to the mobile apps.

Since we started the mobile project, we have been looking for ways to bridge the web and native platforms. One of those is being able to share styles.

I’m aware of solutions like react-native-web or ReactXP, but their approach seems to be making the web code look more like React Native. However, we are ultimately building a tool to build websites, so we lean more towards web technologies. Some of those styles are also likely to be shared with the frontend of the site, and we don’t want to change how that’s done and force React components into millions of websites.

What we do today

React Native uses inline styles and a flexbox-based layout system named Yoga. It is different syntax, but it’s easy to tell how it’s all inspired by the web. The style property names ofter match the CSS equivalents, and Yoga works like flexbox. So we started writing those in CSS with the hope that we’d be able to eventually reuse CSS files for web and native.

/* Some of the first CSS in the project */
.toolbar {
	height: 34;
	background-color: white;
	flex-direction: row;
	justify-content: space-between;
	padding-left: 20;
	padding-right: 20;

To make this work, we use a transformer that takes the CSS file and converts it to a JavaScript object when imported. We also get Sass support in the process so we can use variables and other nice things. This uses css-to-react-native-transform which converts CSS text into objects, and in turn uses css-to-react-native, which converts individual declarations to properties that React Native can understand.

// When you import a CSS file...
import style from './style.scss';

//'s as if you defined an object with those properties
const style = {
    toolbar: {
        height: 34,
        backgroundColor: 'white',
        flexDirection: 'row',
        justifyContent: 'space-between',
        paddingLeft: 20,
        paddingRight: 20

Sharing CSS is more challenging than that

Just because it looks similar, it doesn’t mean that we can now take any CSS from the web and use it on mobile. This solution works by turning class selectors into keys for the style object (so .toolbar becomes style.toolbar), but it can’t process any other kind of CSS selector. Because of that, there’s no concern for the CSS cascade or any sort of inherited values. This is problem #1: what style applies to what element.

Once you match a specific declaration block to a component, the system still needs to be able to understand all the declarations and transform them to valid values for React Native. This works correctly if you write the CSS with React Native in mind, but there is a lot that it’s not supported. For instance, you can’t use calc() and if you dare to use a unit other than px, you get a crash. Problem #2 is not failing when there is something that isn’t supported.

Scoping component CSS

The main challenge that we have when matching a CSS rule to a specific element is that CSS is designed to be global to a web document, and resolving a selector becomes an impossible task without a DOM that keeps track of relationships. Matching a simple class or element selector is achievable, but when you start using combinators or pseudo selectors, it quickly becomes an impossible task, since a component doesn’t know much about where it sits in the hierarchy. If this were to work, it would probably have to be implemented in the React Native renderer.

We did some experiments in supporting descendant and other basic selectors using React contexts, but it seemed like a big effort with not enough cross-platform potential.

Short of React Native implementing an official support for this in the engine, I think our best bet is to let go of selectors and rely on another system to match a specific style to a component. That could be CSS Modules, or a CSS-in-JS solution like Emotion or Styled Components.

Compatible style declarations

I think one of the most important factors for the success of CSS might have been his error handling model. Because different browsers implement new features at a different pace, they will often encounter CSS that they don’t understand yet.

When errors occur in CSS, the parser attempts to recover gracefully, throwing away only the minimum amount of content before returning to parsing as normal. This is because errors aren’t always mistakes—new syntax looks like an error to an old parser, and it’s useful to be able to add new syntax to the language without worrying about stylesheets that include it being completely broken in older UAs.

CSS Syntax Module Level 3

Our current transformer doesn’t work like this. It won’t ignore things that look unsupported, and it will even crash on some instances of “invalid” declarations. It is very hard to share any CSS with another platform without that error resilience that the CSS standard demands.

If we had a CSS engine that respected the standard, we could have a shared style with unsupported values and a fallback that worked on React Native. In an ideal scenario, the following snippet would apply a 1em left margin on the web (since the latest declaration has precedence) and 12px on React Native (since it would only consider the last declaration that was valid).

.component {
    /* React Native can understand this... */
    margin-left: 12px;
    /* ...but not this */
    margin-left: 1em;

My wishlist for CSS support in React Native

This is only a starting point, and there are a lot of CSS features that we won’t be able to support. All layout in React Native is based on flexbox, so we can hardly support any properties related to CSS Grids. Also, because we transform the CSS during compilation, the result is always static, which leaves out a lot of features that depend on the runtime environment.

I can imagine the transformer returning a DynamicStylesheet instead of plain values, that gets resolved to actual values on render. A great example of this is dark mode. The web supports dark mode through the prefers-color-scheme media query.

.text {
	color: black;
	background: white;

@media (prefers-color-scheme: dark) {
	.text {
		color: white;
		background: black;

This could be transformed to a dynamic style object that looked like this:

const style = new DynamicStyleSheet({
	text: {
		color: new DynamicColorSchemeValue({light: 'black', dark: 'white'}),
		background: new DynamicColorSchemeValue({light: 'white', dark: 'black'}),

const resolvedStyle = useDynamicStyleSheet(dynamicStyles)

This is basically what the react-native-dark-mode API looks like already, but that same concept could be extrapolated to many other dynamic values, like calc(), or any other media queries.

Other feature that would be really useful is the @supports directive for feature queries. This would allow us to share the same CSS, but offer different fallbacks for unsupported styles. You can already see several @supports (position: sticky) in the Gutenberg code to contain styles specific to IE11.

Having all these things in place would not solve every problem. We still won’t be able to support every feature, and sometimes we’ll actually want different styles. Maybe a new @media (react-native) query could help contain those styles. However, this would set enough of a solid foundation that I believe would allow sharing CSS with web components.

Big children

Listening to an interview to Alain de Botton, I caught this part that really describes something that’s been on my mind lately.

One of the kindest things that we can do with our lover is to see them as children. And not to infantilize them, but when we’re dealing with children as parents, as adults, we’re incredibly generous in the way we interpret their behavior.

And if a child says “I hate you,” you immediately go, OK, that’s not quite true. Probably they’re tired, they’re hungry, something’s gone wrong, their tooth hurts, something. We’re looking around for a benevolent interpretation that can just shave off some of the more depressing, dispiriting aspects of their behavior. And we do this naturally with children, and yet we do it so seldom with adults. When an adult meets an adult, and they say, “I’ve not had a good day. Leave me alone,” rather than saying, “OK. I’m just going to go behind the facade of this slightly depressing comment…”

[…] We don’t do that. We take it all completely personally. And so I think the work of love is to try […] to go behind the front of this rather depressing challenging behavior and try and ask where it might’ve come from. Love is doing that work to ask oneself, “Where’s this rather aggressive, pained, noncommunicative, unpleasant behavior come from?” If we can do that, we’re on the road to knowing a little bit about what love really is, I think.

Except not just with your lover. It’s an interesting feeling when you start seeing everyone as bigger children, and their little and big reactions.

It makes it so much easier to have empathy for anyone when you see past their adult facade, and understand we're all just trying to figure out this thing called life, and trying to play the "being an adult" game.

Fungi and the Wood Wide Web

When I read Hope in the Dark1, there was this curious fact that stuck with me.

After a rain mushrooms appear on the surface of the earth as if from nowhere. Many come from a sometimes vast underground fungus that remains invisible and largely unknown. What we call mushrooms, mycologists call the fruiting body of the larger, less visible fungus. Uprisings and revolutions are often considered to be spontaneous, but it is the less visible long-term organising and groundwork – or underground work – that often laid the foundation. Changes in ideas and values also result from work done by writers, scholars, public intellectuals, social activists and participants in social media.

I thought the comparison was beautiful, but more than that I was amazed at this idea of mushrooms being just the visible tip of a much larger hidden organism underground.

Fast forward a year, and I just read The Hidden Life of Trees, which explains how trees stay connected to each other through a fungi network that interconnects their roots to exchange information and goods:

Over centuries, a single fungus can cover many square miles and network an entire forest. The fungal connections transmit signals from one tree to the next, helping the trees exchange news about insects, drought, and other dangers. Science has adopted a term first coined by the journal Nature for Dr. Simard’s discovery of the “wood wide web” pervading our forests

I thought the Wood Wide Web was interesting (and funny), but also there it was: the giant fungi again. And apparently, the largest known organism is a fungus “that occupies some 2,384 acres (965 hectares) of soil in Oregon’s Blue Mountains (…) and is estimated to be 2,400 years old but could be as ancient as 8,650 years, which would earn it a place among the oldest living organisms as well.”

It’s been a fascinating read, full of surprising facts about trees, and I definitely recommend reading it. ⭐️⭐️⭐️⭐️⭐️


Featured image by Aaron Escobar

  1. If you can, buy directly from Haymarket. The paperback includes a eBook copy for free and their eBooks have no DRM. Just like it should be. 

On Human Rights

One of the first things she pointed out is that what was exposed by the refugee crisis of the last century was how so-called human rights were actually political and national rights. So you were only — you only had as many rights as were guarded by the country in which you happened to be born.

Once that country decided to decitizenize you, once it decided that you were no longer a citizen, once it decided that it had no more responsibilities towards you, you were rightless. She said the world — she said very famously, “The world found nothing sacred in the abstract nakedness of being human.”

Lyndsey Stonebridge on Hannah Arendt in this episode of On Being: Thinking and Friendship in Dark Times: Hannah Arendt for Now.