@styleupp/stylex-ui
v0.1.6
Published
Expo-first token-driven UI kit with custom/native render modes.
Maintainers
Readme
Stylex UI — Expo-first Token-Driven React Native UI Kit
A lightweight UI library for Expo/React Native that renders components from design tokens you provide in stylex.json. Each component supports two render modes:
custom(default) — fully token-driven stylingnative(beta stub) — reserved for future platform-native look/feel
This README covers install, setup, usage, tokens, and component APIs.
Contents
- Install
- Quick start
- Tokens (
stylex.json) - Provider
- Usage examples
- Components & props
- TypeScript & builds
- Accessibility & theming
- FAQ
Install
# add the UI library
npm i @styleupp/stylex-ui
# peer deps must already exist in your app (Expo projects have them)
# npm i react react-native expo
# for ParallaxScrollView component, also install:
npx expo install react-native-reanimatedThe library ships ESM+CJS bundles and d.ts typings. It keeps
react,react-native,expo, andreact-native-reanimatedexternal so your app controls versions.
Quick start
- Create tokens in your app root:
stylex.json
{
"$schema": "node_modules/@your-scope/stylex-ui/stylex.schema.json",
"mode": "system",
"light": {
"colors": {
"bg": "#0b0d10",
"card": "#12161b",
"text": "#e7eef6",
"muted": "#8ea0b4",
"accent": "#4da3ff",
"accentText": "#0b0d10",
"border": "rgba(255,255,255,0.08)",
"danger": "#ff6b6b",
"warning": "#ffd166",
"success": "#70e0a3"
},
"radius": { "sm": 8, "md": 12, "lg": 20, "xl": 28 },
"spacing": { "xs": 4, "sm": 8, "md": 12, "lg": 16, "xl": 24, "2xl": 32 },
"font": {
"family": "Inter",
"weight": { "regular": "400", "medium": "500", "semibold": "600", "bold": "700" },
"size": { "xs": 12, "sm": 14, "md": 16, "lg": 18, "xl": 22, "2xl": 28, "3xl": 34 }
},
"shadows": { "sm": 2, "md": 6, "lg": 12 }
},
"dark": { "...": "define a matching dark palette" },
"components": {
"Button": { "radius": "lg" },
"Text": { "tracking": { "tight": -0.2, "normal": 0, "wide": 0.2 } }
}
}- Wrap your app with the single provider:
// App.tsx
import React from "react";
import { SafeAreaView, View } from "react-native";
import stylex from "./stylex.json";
import { StylexProvider, Card, Button, Text, Heading, Stack } from "@your-scope/stylex-ui";
export default function App() {
return (
<StylexProvider config={stylex} mode="custom">
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, padding: 16 }}>
<Stack gap="lg">
<Heading>Stylex UI</Heading>
<Text>Unified look from tokens.</Text>
<Card>
<Text kind="caption" muted tracking="wide">Inside card</Text>
<Button label="Tap me" />
<Button label="Native (stub)" use="native" />
</Card>
</Stack>
</View>
</SafeAreaView>
</StylexProvider>
);
}- Use components — everything is tokenized, with optional
use="native"per instance.
Tokens (stylex.json)
Shape
mode:"system" | "light" | "dark"— choose how the library selects the color scheme.light/dark:colors: arbitrary named color tokens (bg,card,text,muted,accent,accentText,border,danger,warning,success, …).radius:{ sm, md, lg, xl }numbers (px).spacing:{ xs, sm, md, lg, xl, 2xl }numbers (px).font:{ family, weight: { regular, medium, semibold, bold }, size: { xs..3xl } }.shadows(optional):{ sm, md, lg }elevation values.
components(optional): per-component preferences (e.g.,Button.radius,Text.tracking).
Loading custom fonts
If you set a custom font.family, load it before render:
import * as Font from "expo-font";
await Font.loadAsync({ Inter: require("./assets/Inter.ttf") });Provider
<StylexProvider
config={stylex} // object loaded from stylex.json
mode="custom" // "custom" | "native" (global default; per-component override via `use`)
>
{children}
</StylexProvider>- Responds to system theme changes when
mode: "system"in tokens. - Hook:
const { colors, spacing, radius, font, px, r, fs, scheme, renderMode } = useStylex();
Usage examples
<Button label="Buy now" variant="solid" />
<Button label="Outline" variant="outline" />
<Button label="Ghost" variant="ghost" size="sm" />
<Text kind="display" weight="bold">Headline</Text>
<Text kind="overline" tracking="wide" muted>OVERLINE</Text>
<Text kind="code" monospace>npm run build</Text>
<Card><Text>Card body</Text></Card>
<TextInput placeholder="Email" />
<Switch value={on} onValueChange={setOn} />
<Checkbox checked={agree} onChange={setAgree} />
<Badge label="New" tone="accent" />
<Chip label="React Native" selected onClose={() => {}} />
<Avatar label="Ada Lovelace" />
<Progress value={42} />
<Modal visible={open} onClose={() => setOpen(false)}><Text>Hi</Text></Modal>
<Tabs items={[{key:"a",label:"Tab A"}, {key:"b",label:"Tab B"}]} value={tab} onChange={setTab} />
<ToastProvider>{/* call useToast().show({ text: "Saved!" }) */}</ToastProvider>Components & props
Typography
Textkind:"display"|"title"|"header"|"subheader"|"body"|"caption"|"overline"|"code"(default"body")weight:"regular"|"medium"|"semibold"|"bold"tracking:"tight"|"normal"|"wide"muted:boolean— usecolors.mutedmonospace:boolean— force monospaced font (or setkind="code")use:"custom"|"native"
Headinglevel:1|2|3|4|5|6(maps to text sizes)- other props: same as
Text
Layout & primitives
Stackgap: spacing keydirection:"row"|"column"
Row/Columngap,align,justify
Boxbg: color token name or hexp,px,py: spacing keysr: radius keyborder:boolean
Dividervertical:booleansize: number (thickness)
ScrollView- extends RN
ScrollViewProps contentGap,contentPadding: spacing keys
- extends RN
ParallaxScrollView- extends
ScrollViewProps header: ReactNode (header content with parallax effect)headerHeight: number (default: 250)headerBackgroundColor: string (optional, defaults to theme bg)contentPadding: spacing key (default: "lg")contentGap: spacing key (default: "md")enableScale: boolean (enable scale effect on header, default: true)- Note: Requires
react-native-reanimated>=3 peer dependency
- extends
Inputs
Buttonlabel: stringsize:"sm"|"md"|"lg"variant:"solid"|"outline"|"ghost"disabled,onPress,use
TextInput- extends RN
TextInputProps invalid: booleanleft,right: ReactNodesize:"sm"|"md"|"lg"use
- extends RN
Switch- extends RN
SwitchProps use
- extends RN
Checkboxchecked: booleanonChange(v: boolean)disabledsize:"sm"|"md"|"lg"use
Select<T>value?: TonChange(v: T)options: {label:string, value:T}[]placeholder?: string
RadioGroup<T>items: {label:string, value:T, disabled?:boolean}[]value?: TonChange(v: T)
Slidervalue: number,onChange(v:number)min,max,step,height
Data display
Card— container withcolors.card, border, radius.Badgelabeltone:"neutral"|"success"|"warning"|"danger"|"accent"
Chiplabel: stringselected?: booleanonPress?: () => voidonClose?: () => void— show close buttonstyle?: ViewStyle
Avataruri?: string,label?: string(initials)size?: numberrounded:"sm"|"md"|"lg"|"xl"|"full"
ListItemtitle,subtitle?left?,right?onPress?
Feedback
Spinnersize:"small"|"large"|number
Progressvalue: 0–100,height?
Skeletonstyle?,rounded?
Overlays
Modalvisible,onClose?,dismissOnBackdrop?
Sheetopen,onClose?,height?
Tooltipcontent,children
Navigation
Tabsitems: { key, label, disabled?, badge? }[]value: string,onChange(key)variant:"underline"|"pill"
Icons & actions
Iconname?: string(emoji or mapped glyph),uri?: string,size?: number,color?
IconButtonicon: ReactNode|string,onPress?,size?,variant:"ghost"|"solid"
IconTextButtonicon: ReactNode|string,label,onPress?
ActionButtonicon?: ReactNode|string,onPress?— floating primary action
Disclosure
Collapsibleopen,duration?
Accordionitems: { key, title, content }[]value?,onChange?(key)
Misc
ExternalLinkhref,label
ErrorBoundaryfallback?(ReactNode)
Toast (provider + hook)
ToastProvider— place near the app rootuseToast().show({ text, tone? })
TypeScript & builds
- You can import JSON tokens directly (
import stylex from "./stylex.json";). - All components are typed. You can inspect
StylexConfigif you generate tokens dynamically. - If you build your own fork/package, make sure your bundler keeps
react,react-native, andexpoexternal (already configured).
Accessibility & theming
- Touch targets aim for ≥ 44×44 where interactive.
- Text contrast is controlled by your tokens. Prefer accessible pairs (
colors.textvscolors.card). - RTL: primitives avoid directional assumptions; if you introduce directional padding, consult
I18nManager.isRTL. - Theme switching: set
mode: "system"in tokens for automatic scheme changes; or force"light"/"dark".
FAQ
Can I override render mode per component?
Yes, pass use="native" on any component. Global default is the mode prop on StylexProvider.
Do I need to ship stylex.json with the app?
You load it at build time (import stylex from "./stylex.json"), then pass the object to the provider.
Can I extend tokens with new keys?
Yes—colors, radius, spacing, and font can contain additional keys. Components only read the token names they use.
Does it work on web? Yes—everything is built from RN primitives and Expo. Some platform nuances (focus rings, hover) are minimal by design.
Contributing
- Keep new components dependency-free when possible.
- Follow the pattern: tokenized custom branch + stubbed native branch with
useoverride. - Add props to this README and minimal examples.
License
MIT.
