
Why I'm Building Artic Native: A Zero-Runtime UI Library for React Native
May 15, 2026
I’ve audited enough React Native codebases to know the problem isn’t a shortage of UI kits—it’s the weight of them. Most ship runtime styling engines, untyped theme objects, and animations that choke the JS thread. For my 2026 apps, I needed something colder. Immutable. Fast. Opinionated without being oppressive.
Enter artic-native.
This is not a “another button” package. It’s a set of primitives engineered to hit 60fps even on mid-range Android hardware, without giving up the design guardrails teams need.
The Architecture Playbook
artic-native is built on three guiding rules:
- Zero-Runtime Styling — Tokens compile to frozen
StyleSheetobjects and can be statically extracted into native modules for Hermes. - Animation-First Primitives — Every interactive component embraces
react-native-reanimated3. Layout transitions, springs, and shared element hooks are first-class. - Contracts over Config — Props are strict TypeScript interfaces. Variants are union types. No arbitrary
styleinjection that breaks consistency.
// packages/artic-native/src/foundations/tokens.ts
export const ArticTokens = {
radius: { sm: 8, md: 12, lg: 20 },
spacing: { xs: 8, sm: 12, md: 16, lg: 24 },
palette: {
ink: "#0F172A",
frost: "#E2E8F0",
glacier: "#38BDF8",
polar: "#F8FAFC",
},
typography: {
label: { fontFamily: "Inter-SemiBold", fontSize: 12, letterSpacing: 0.6 },
body: { fontFamily: "Inter-Regular", fontSize: 16, lineHeight: 22 },
},
} as const;
Tokens ship as pure data. When consumers opt into “frozen mode,” the library emits a generated .native.ts file that compiles to JSI bindings—zero runtime cost when the app boots.
Primitive Spotlight
1. ArticButton: Haptic-Ready Call To Action
Buttons should feel alive. Press feedback, haptics, and shadow interpolation are built-in.
// packages/artic-native/src/primitives/ArticButton.tsx
import React from "react";
import { Pressable, Text, StyleSheet } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from "react-native-reanimated";
import * as Haptics from "expo-haptics";
import { ArticTokens } from "../foundations/tokens";
type ButtonVariant = "solid" | "outline" | "glass";
export interface ArticButtonProps {
label: string;
variant?: ButtonVariant;
onPress?: () => void;
disabled?: boolean;
}
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
export const ArticButton = ({ label, variant = "solid", onPress, disabled }: ArticButtonProps) => {
const scale = useSharedValue(1);
const pressIn = () => {
if (disabled) return;
scale.value = withSpring(0.95, { stiffness: 220 });
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
};
const pressOut = () => {
scale.value = withSpring(1, { stiffness: 180 });
};
const rStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<AnimatedPressable
accessibilityRole="button"
style={[styles.base, styles[variant], disabled && styles.disabled, rStyle]}
onPress={disabled ? undefined : onPress}
onPressIn={pressIn}
onPressOut={pressOut}
>
<Text style={[styles.label, variant= "solid" ? styles.labelOnDark : styles.labelOnLight]}>
{label}
</Text>
</AnimatedPressable>
);
};
const styles = StyleSheet.create({
base: {
paddingVertical: ArticTokens.spacing.sm,
paddingHorizontal: ArticTokens.spacing.lg,
borderRadius: ArticTokens.radius.md,
alignItems: "center",
justifyContent: "center",
shadowColor: "#000000",
shadowOpacity: 0.1,
shadowRadius: 10,
shadowOffset: { width: 0, height: 4 },
},
solid: { backgroundColor: ArticTokens.palette.ink },
outline: {
backgroundColor: "transparent",
borderWidth: 1,
borderColor: ArticTokens.palette.frost,
},
glass: {
backgroundColor: "rgba(255,255,255,0.12)",
borderWidth: 1,
borderColor: "rgba(255,255,255,0.24)",
},
disabled: { opacity: 0.5 },
label: {
...ArticTokens.typography.body,
fontWeight: "600",
},
labelOnDark: { color: "#FFFFFF" },
labelOnLight: { color: ArticTokens.palette.ink },
});
2. ArticInput: Focus States as a Service
Forms are where UX dies. ArticInput handles floating labels, focus outlines, and validation messaging without leaking state outside.
// packages/artic-native/src/primitives/ArticInput.tsx
import React, { useState } from "react";
import { TextInput, View, Text, StyleSheet, TextInputProps } from "react-native";
import { ArticTokens } from "../foundations/tokens";
export interface ArticInputProps extends TextInputProps {
label: string;
error?: string;
}
export const ArticInput = ({ label, error, ...props }: ArticInputProps) => {
const [focused, setFocused] = useState(false);
return (
<View style={styles.container}>
<Text style={[styles.label, focused && styles.labelFocused, !!error && styles.labelError]}>
{label.toUpperCase()}
</Text>
<TextInput
style={[styles.input, focused && styles.inputFocused, !!error && styles.inputError]}
placeholderTextColor="#94A3B8"
onFocus={()=> setFocused(true)}
onBlur={()=> setFocused(false)}
{...props}
/>
{!!error && <Text style={styles.errorText}>{error}</Text>}
</View>
);
};
const styles = StyleSheet.create({
container: { marginBottom: ArticTokens.spacing.md },
label: {
...ArticTokens.typography.label,
color: "#64748B",
marginBottom: 6,
},
labelFocused: { color: ArticTokens.palette.glacier },
labelError: { color: "#EF4444" },
input: {
height: 52,
borderWidth: 1,
borderRadius: ArticTokens.radius.sm,
borderColor: ArticTokens.palette.frost,
backgroundColor: ArticTokens.palette.polar,
paddingHorizontal: ArticTokens.spacing.md,
...ArticTokens.typography.body,
},
inputFocused: { borderColor: ArticTokens.palette.glacier, backgroundColor: "#FFFFFF" },
inputError: { borderColor: "#EF4444", backgroundColor: "#FEF2F2" },
errorText: { color: "#EF4444", fontSize: 12, marginTop: 4 },
});
3. ArticSurface: Layout Primitive with Motion Hooks
Every app needs cards and sheets. ArticSurface packages elevation, rounded corners, and shared element helpers.
// packages/artic-native/src/primitives/ArticSurface.tsx
import React from "react";
import { View, StyleSheet, ViewProps } from "react-native";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
import { ArticTokens } from "../foundations/tokens";
type Elevation = "base" | "floating" | "overlay";
interface ArticSurfaceProps extends ViewProps {
elevation?: Elevation;
asAnimated?: boolean;
}
const surfaceStyles: Record<Elevation, object> = {
base: {
shadowOpacity: 0.04,
shadowRadius: 6,
shadowOffset: { width: 0, height: 2 },
},
floating: {
shadowOpacity: 0.08,
shadowRadius: 18,
shadowOffset: { width: 0, height: 12 },
},
overlay: {
shadowOpacity: 0.18,
shadowRadius: 24,
shadowOffset: { width: 0, height: 20 },
},
};
export const ArticSurface = ({ elevation = "base", style, asAnimated, ...rest }: ArticSurfaceProps) => {
const Component = asAnimated ? Animated.View : View;
return (
<Component
entering={asAnimated ? FadeIn.duration(180) : undefined}
exiting={asAnimated ? FadeOut.duration(160) : undefined}
style={[styles.base, surfaceStyles[elevation], style]}
{...rest}
/>
);
};
const styles = StyleSheet.create({
base: {
borderRadius: ArticTokens.radius.lg,
backgroundColor: "#FFFFFF",
padding: ArticTokens.spacing.lg,
},
});
Packaging for the Edge
- Tree Shaking: Every primitive ships as an ES module; consumers can
import { ArticButton } from "artic-native/button"to avoid bundling the universe. - Theme Composer: A CLI (
artic-native init) snapshots your brand palette and spits out a read-only theme artifact. - Hermes RIC: Reanimated + zero-runtime tokens means Hermes can keep UI logic inside the JSI sandbox—no bridge thrash.
npx artic-native init --namespace xaid --tokens ./design/tokens.json
npx artic-native build --platform ios --platform android
What’s Next
I’m rounding out the release with:
- Slot-Based Layouts powered by React 19
usefor deferred children. - Devtools Integration that logs component prop usage to a Supabase table for design audits.
- Storybook Native recipes so designers can drive builds from Figma tokens directly.
The repo is private while I finish the tree-shaking benchmarks, but the waitlist is open. Drop your email on xaid.in/contact and you’ll get early access to artic-native + the sample expo app.