Root Native — why I built the UI and Inertia libraries for React Native
The thinking behind the Root Native organisation, the specific problems Root Native UI and Inertia solve, and how they differ from what already exists.
I build React Native apps for a living. After enough of them, a pattern shows up that is hard to unsee: I kept writing the same code, in the same order, for every new project. The same button. The same input. The same sheet. The same fade-in on mount.
Root Native is where I stopped rewriting it.
The organisation holds the libraries I now reach for first. Two of them are public: UI, a component library, and Inertia, an animation layer. They solve different problems, but they come from the same source — the distance between what a framework gives you and what an application actually needs.
The problem with opinionated component libraries
Every component library is a good fit until the brand guidelines arrive.
This is the part I got wrong for years. You pick a library, you like its defaults, and you build three screens quickly. Then a designer hands over a spec, and the defaults become the enemy. You override a colour and find it hard-coded two levels down. You want a different border radius on one button and the theme only offers a global one. You end up fighting the library you chose to save time.
Root Native UI starts from the opposite end. It is design-system agnostic. Material Design 3 ships out of the box, so a new project looks right on day one — but MD3 is a default, not a requirement. The theme engine adapts to your design language rather than imposing its own. You can use MD3, bring your own system, or mix both in the same app.
The override path matters as much as the defaults. You can change things at three levels:
- the theme, when the change is global
- the prop, when the change belongs to one usage
- the style, when you need to reach past both
Nothing is sealed. When you need to go one level deeper, there is a level to go to.
What else you get
- A growing set of components, on Expo and bare React Native
- Per-component subpath exports, so your bundle only carries what you import
- Accessibility roles, labels and states built in rather than bolted on
- Strict types on every prop and every theme token
There is also a CLI. npx rootnative create scaffolds a project, and
rootnative add copies a component into your codebase — source and all.
That second command is the important one. The component becomes your file, in
your repository, that you can edit. You are not waiting on me to merge a pull
request so you can change a padding value.
The problem with animations
Reanimated is excellent software. I am not trying to replace it, and Inertia is built directly on top of Reanimated 4.
But the worklet model asks a lot for a simple job. To fade an element in, you
reach for a shared value, a useAnimatedStyle hook, and a worklet — a mental
model borrowed from the UI thread, applied to something that conceptually is
just "appear."
Inertia makes animations props:
import { Motion } from '@rootnative/inertia'
export function Card() {
return (
<Motion.View
animate={{ opacity: 1, translateY: 0 }}
transition={{ tension: 180, friction: 22 }}
/>
)
}Drop in a Motion primitive, give it an animate prop, and the spring runs on
the UI thread. Shared values, worklets and useAnimatedStyle stay where they
belong — inside the library.
How it differs from the other wrappers
Wrapping Reanimated is not a new idea. Inertia exists because of specific sharp edges I kept hitting in the wrappers that already existed.
Style types narrow per primitive. Motion.View takes a ViewStyle and
Motion.Text takes a TextStyle. Pass the wrong one and it fails at compile
time, not in a simulator twenty minutes later.
Spring parameters use a vocabulary people already know. Inertia takes
tension, friction, mass and velocity — react-spring's language — rather
than Reanimated's raw stiffness and damping. Most React developers have
tuned a react-spring animation before. That knowledge should transfer.
One gesture prop shape. pressed, focused and hovered exist on every
primitive. No separate pressable variant, no whileTap/whilePress soup to
remember.
Re-renders stay cheap. A memoized worklet factory hashes the resolved
animate and transition objects, so a re-render with unchanged values
allocates no new UI-thread closures.
Bundle size is verified in CI. Every primitive is a tree-shakable subpath, and the size is checked on every commit rather than assumed.
Mount, exit, per-property transitions and gestures all share one shape. Learn it once.
Getting the most out of them
A few things are worth knowing early, because they change how you use the libraries rather than just what you call.
Import from subpaths. Both libraries export per-component and per-primitive subpaths. Import what you use and the bundler drops the rest. This is free, but only if you skip the barrel import.
Use rootnative add when you want to own a component. Installing the
package is right for most components. But when one needs to diverge from what I
shipped, copy it in with its source and edit it. A vendored component you
control beats a wrapper fighting a prop that does not exist.
Set the theme once, at the top. Most overrides that people write per-usage belong in the theme. If you write the same prop three times, it is a theme value.
Let the types work. Both libraries are strict on purpose. If a prop is
rejected, that is usually a real bug caught early — a TextStyle on a view, a
token that does not exist.
Use the two together, but not because you have to. They share an organisation, not a dependency. Inertia has no idea UI exists. Adopt either one on its own.
Where this is going
Both are MIT licensed and public:
- UI — github.com/rootnative/ui · docs · live demo
- Inertia — github.com/rootnative/inertia · docs · npm
The goal for Root Native is not a large collection of packages. It is a small set of libraries that hold up in real applications, where the defaults are good enough to start with and the escape hatches are real when you need them.
If you use either one and hit a wall, open an issue. The walls are the most useful thing you can send me.