npm package: @styleupp/stylex-ui
Description: Expo-first token-driven UI kit with custom/native render modes.
Latest version: 0.1.6

### 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 styling
* `native` *(beta stub)* — reserved for future platform-native look/feel

This README covers install, setup, usage, tokens, and component APIs.

---

### Contents

* [Install](#install)
* [Quick start](#quick-start)
* [Tokens (`stylex.json`)](#tokens-stylexjson)
* [Provider](#provider)
* [Usage examples](#usage-examples)
* [Components & props](#components--props)
* [TypeScript & builds](#typescript--builds)
* [Accessibility & theming](#accessibility--theming)
* [FAQ](#faq)

---

### Install

```bash
# 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-reanimated
```

> The library ships ESM+CJS bundles and d.ts typings. It keeps `react`, `react-native`, `expo`, and `react-native-reanimated` external so your app controls versions.

---

### Quick start

1. **Create tokens** in your app root: `stylex.json`

```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 } }
  }
}
```

2. **Wrap your app** with the single provider:

```tsx
// 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>
  );
}
```

3. **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:

```ts
import * as Font from "expo-font";
await Font.loadAsync({ Inter: require("./assets/Inter.ttf") });
```

---

### Provider

```tsx
<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

```tsx
<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

* `Text`

  * `kind`: `"display"|"title"|"header"|"subheader"|"body"|"caption"|"overline"|"code"` (default `"body"`)
  * `weight`: `"regular"|"medium"|"semibold"|"bold"`
  * `tracking`: `"tight"|"normal"|"wide"`
  * `muted`: `boolean` — use `colors.muted`
  * `monospace`: `boolean` — force monospaced font (or set `kind="code"`)
  * `use`: `"custom"|"native"`
* `Heading`

  * `level`: `1|2|3|4|5|6` (maps to text sizes)
  * other props: same as `Text`

#### Layout & primitives

* `Stack`

  * `gap`: spacing key
  * `direction`: `"row"|"column"`
* `Row` / `Column`

  * `gap`, `align`, `justify`
* `Box`

  * `bg`: color token name or hex
  * `p`, `px`, `py`: spacing keys
  * `r`: radius key
  * `border`: `boolean`
* `Divider`

  * `vertical`: `boolean`
  * `size`: number (thickness)
* `ScrollView`

  * extends RN `ScrollViewProps`
  * `contentGap`, `contentPadding`: spacing keys
* `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

#### Inputs

* `Button`

  * `label`: string
  * `size`: `"sm"|"md"|"lg"`
  * `variant`: `"solid"|"outline"|"ghost"`
  * `disabled`, `onPress`, `use`
* `TextInput`

  * extends RN `TextInputProps`
  * `invalid`: boolean
  * `left`, `right`: ReactNode
  * `size`: `"sm"|"md"|"lg"`
  * `use`
* `Switch`

  * extends RN `SwitchProps`
  * `use`
* `Checkbox`

  * `checked`: boolean
  * `onChange(v: boolean)`
  * `disabled`
  * `size`: `"sm"|"md"|"lg"`
  * `use`
* `Select<T>`

  * `value?: T`
  * `onChange(v: T)`
  * `options: {label:string, value:T}[]`
  * `placeholder?: string`
* `RadioGroup<T>`

  * `items: {label:string, value:T, disabled?:boolean}[]`
  * `value?: T`
  * `onChange(v: T)`
* `Slider`

  * `value: number`, `onChange(v:number)`
  * `min`, `max`, `step`, `height`

#### Data display

* `Card` — container with `colors.card`, border, radius.
* `Badge`

  * `label`
  * `tone`: `"neutral"|"success"|"warning"|"danger"|"accent"`
* `Chip`

  * `label`: string
  * `selected?: boolean`
  * `onPress?: () => void`
  * `onClose?: () => void` — show close button
  * `style?: ViewStyle`
* `Avatar`

  * `uri?: string`, `label?: string` (initials)
  * `size?: number`
  * `rounded`: `"sm"|"md"|"lg"|"xl"|"full"`
* `ListItem`

  * `title`, `subtitle?`
  * `left?`, `right?`
  * `onPress?`

#### Feedback

* `Spinner`

  * `size`: `"small"|"large"|number`
* `Progress`

  * `value`: 0–100, `height?`
* `Skeleton`

  * `style?`, `rounded?`

#### Overlays

* `Modal`

  * `visible`, `onClose?`, `dismissOnBackdrop?`
* `Sheet`

  * `open`, `onClose?`, `height?`
* `Tooltip`

  * `content`, `children`

#### Navigation

* `Tabs`

  * `items: { key, label, disabled?, badge? }[]`
  * `value: string`, `onChange(key)`
  * `variant`: `"underline"|"pill"`

#### Icons & actions

* `Icon`

  * `name?: string` *(emoji or mapped glyph)*, `uri?: string`, `size?: number`, `color?`
* `IconButton`

  * `icon: ReactNode|string`, `onPress?`, `size?`, `variant`: `"ghost"|"solid"`
* `IconTextButton`

  * `icon: ReactNode|string`, `label`, `onPress?`
* `ActionButton`

  * `icon?: ReactNode|string`, `onPress?` — floating primary action

#### Disclosure

* `Collapsible`

  * `open`, `duration?`
* `Accordion`

  * `items: { key, title, content }[]`
  * `value?`, `onChange?(key)`

#### Misc

* `ExternalLink`

  * `href`, `label`
* `ErrorBoundary`

  * `fallback?` (ReactNode)

#### Toast (provider + hook)

* `ToastProvider` — place near the app root
* `useToast().show({ text, tone? })`

---

### TypeScript & builds

* You can import JSON tokens directly (`import stylex from "./stylex.json";`).
* All components are typed. You can inspect `StylexConfig` if you generate tokens dynamically.
* If you build your own fork/package, make sure your bundler keeps `react`, `react-native`, and `expo` **external** (already configured).

---

### Accessibility & theming

* Touch targets aim for ≥ 44×44 where interactive.
* Text contrast is controlled by your tokens. Prefer accessible pairs (`colors.text` vs `colors.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 `use` override.
* Add props to this README and minimal examples.

---

### License

MIT.