Building an AI-Assisted React Native UI Kit — Why Tokens Beat Pixels in the Age of Copilot

typescript dev.to

We're in an era where developers write UI with AI. You describe a screen, Claude or Copilot generates the code, and you ship it. But there's a gap nobody talks about:

AI-generated UI is inconsistent.

Ask it to build a login screen — it picks padding: 16. Ask it to build a settings screen — it picks padding: 20. Ask it for a card — borderRadius: 8. Another card — borderRadius: 12. Same app, different screens, zero consistency.

The problem isn't the AI. It's that there's no vocabulary to constrain it.

Tokens over pixels

This is the idea behind @esaltws/react-native-salt — a React Native UI system where every visual property is a named token from a finite set.

Instead of:

<View style={{ padding: 16, borderRadius: 8, gap: 12 }}>
Enter fullscreen mode Exit fullscreen mode

You write:

<Stack gap="md" style={{ padding: spacing.lg }}>
Enter fullscreen mode Exit fullscreen mode

"md", "lg", "sm" — these are the only options. No guessing. No magic numbers. When AI reads the TypeScript types or this documentation, it picks from the same set every time.

Here's what the token scales look like:

spacing:    none | xs(4) | sm(8) | md(12) | lg(16) | xl(24) | xxl(32)
radius:     none | sm(6) | md(10) | lg(14) | xl(20) | xxl(24) | pill(999)
fontSizes:  xxs(10) | xs(12) | sm(14) | md(16) | lg(18) | xl(20) | xxl(24) | 3xl(32) | 4xl(40)
Enter fullscreen mode Exit fullscreen mode

Colors are tokens too — intent="primary" instead of color="#2563eb". Size is "sm" | "md" | "lg" everywhere. The vocabulary is small, strict, and typed.

119 components, same patterns

Salt ships 119 components across 23 categories — layout, forms, navigation, charts, feedback, data display, and more. They all follow the same conventions:

// Every interactive component
<Button title="Save" intent="primary" size="md" variant="solid" />
<Badge label="New" intent="success" size="sm" />
<Input label="Email" size="md" error={errors.email} />

// Every layout component
<Stack gap="md" />
<Row gap="sm" align="center" />
<Card elevation={2} />
Enter fullscreen mode Exit fullscreen mode

Learn the pattern once — intent for color, size for dimensions, variant for style — and you know how every component works. More importantly, AI knows how every component works.

Branded themes from one hex color

Here's where salt-theme-gen comes in. Most design systems hand you a default blue theme and tell you to manually pick 17 color tokens for light mode and another 17 for dark mode. That's tedious and error-prone.

salt-theme-gen generates a complete light/dark color system from a single hex color using OKLCH perceptual color science:

import { generateTheme } from "salt-theme-gen";
import { SaltProvider } from "@esaltws/react-native-salt";

const branded = generateTheme({
  primary: "#0E9D8E",
  harmony: "complementary",
});

export default function App() {
  return (
    <SaltProvider lightTheme={branded.light} darkTheme={branded.dark}>
      {/* All 119 components now use your brand */}
    </SaltProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pick a color. Pick a harmony mode (analogous, triadic, split-complementary, tetradic, monochromatic). You get mathematically balanced light and dark themes. Done.

No color picker sessions. No Figma token exports. One line of config.

How AI actually uses this

When you add Salt to your project, here's what changes in the AI workflow:

Before Salt — you prompt: "Build me a settings screen"
AI generates random padding, hardcoded hex colors, inconsistent border radius. You spend 20 minutes cleaning it up.

With Salt — same prompt, but AI reads the types and README:

<Screen scroll>
  <Stack gap="lg" style={{ padding: spacing.lg }}>
    <SectionHeader title="Account" />
    <Card>
      <Stack gap="md">
        <ListItem title="Profile" right={<Icon name="chevron-forward" />} onPress={goProfile} />
        <ListItem title="Notifications" right={<Switch value={notifs} onValueChange={setNotifs} />} />
      </Stack>
    </Card>
    <SectionHeader title="Appearance" />
    <Card>
      <Stack gap="md">
        <ThemeSwitcher />
        <FontLevelSwitcher />
      </Stack>
    </Card>
  </Stack>
</Screen>
Enter fullscreen mode Exit fullscreen mode

No hex codes. No pixel values. Consistent with every other screen in the app. The README acts as the AI's style guide.

Getting started

1. Install

npm install @esaltws/react-native-salt
npm install salt-theme-gen  # optional, for branded themes
Enter fullscreen mode Exit fullscreen mode

2. Generate your brand theme (optional)

If you want your own colors instead of the defaults, salt-theme-gen creates a full light/dark color system from one hex value:

// theme.ts
import { generateTheme } from "salt-theme-gen";

export const brandTheme = generateTheme({
  primary: "#0E9D8E",            // your brand color
  harmony: "complementary",      // color harmony strategy
});

// brandTheme.light and brandTheme.dark are now complete
// theme objects with primary, secondary, danger, success,
// warning, info, background, surface, text, muted, border
// — all derived from your single hex using OKLCH color math
Enter fullscreen mode Exit fullscreen mode

Available harmonies: analogous, triadic, split-complementary, tetradic, complementary, monochromatic. Each produces a different secondary color relationship while keeping all semantic colors (danger, success, warning, info) usable and accessible.

3. Wrap your app

Pass the generated theme to SaltProvider. If you skip step 2, Salt uses sensible defaults.

// App.tsx or app/_layout.tsx
import { SaltProvider } from "@esaltws/react-native-salt";
import { brandTheme } from "./theme";

export default function App() {
  return (
    <SaltProvider
      lightTheme={brandTheme.light}
      darkTheme={brandTheme.dark}
    >
      <Slot />
    </SaltProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

That's it. All 119 components now use your brand colors. Dark mode works automatically. Every intent="primary" Button, Badge, ProgressBar, and Toast picks up the generated palette.

4. Use components

import { Screen, Stack, Card, Title, Text, Button, Input, Badge } from "@esaltws/react-native-salt";

export default function SignUp() {
  return (
    <Screen scroll>
      <Stack gap="lg" style={{ padding: 16 }}>
        <Title>Create Account</Title>
        <Card elevation={2}>
          <Stack gap="md">
            <Input label="Email" placeholder="you@example.com" required />
            <Input label="Password" secureTextEntry required />
            <Button title="Sign Up" intent="primary" fullWidth />
          </Stack>
        </Card>
        <Badge intent="info">Already have an account? Log in</Badge>
      </Stack>
    </Screen>
  );
}
Enter fullscreen mode Exit fullscreen mode

No hex codes anywhere. Change your brand color in theme.ts, and every screen updates.

What you get

  • 119 components — layout, forms, navigation, charts, feedback, data display, commerce, auth, chat, and more
  • Token-driven — spacing, radius, font sizes, icon sizes, component sizes all from named scales
  • Light/dark mode — follows device, overridable at runtime
  • Font scaling — all sizes adapt to accessibility settings (fontLevel 8–18)
  • Zero native dependencies — pure React Native, works in Expo Go
  • TypeScript strict — every prop typed, every token a union
  • 932 tests — across 122 test suites

The full API reference is in the README. It's long on purpose — it's the AI's style guide as much as yours.


Try the demo: Download APK

Packages:

Source: dev.to

arrow_back Back to Tutorials