npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spelyco/react-native

v1.1.0

Published

React Native UI components for Spelyco, powered by Unistyles 3

Readme

@spelyco/react-native

React Native UI components for Spelyco, powered by Unistyles 3.


Install

bun add @spelyco/react-native \
  @react-native-async-storage/async-storage \
  expo-status-bar \
  expo-system-ui \
  react-native-unistyles \
  react-native-edge-to-edge \
  react-native-nitro-modules \
  react-native-reanimated

This package is published as TypeScript source. Your bundler (Metro for React Native / Expo) transpiles it directly. Make sure your Babel config processes it (see Setup below).


Setup

1. Babel plugin (required)

Unistyles 3 ships a Babel plugin that wires up the C++ shadow tree bindings. Because this package is shipped as source, you must include it in autoProcessPaths so the plugin processes our files inside node_modules.

babel.config.js:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "react-native-unistyles/plugin",
        {
          root: "src",
          autoProcessPaths: ["@spelyco/react-native"],
        },
      ],
    ],
  };
};

2. Wrap your app with SpelycoProvider

SpelycoProvider boots Unistyles, owns the color scheme state (zustand + AsyncStorage persistence), and applies the status/system bars from the theme. Put it at the root — for Expo Router that means app/_layout.tsx:

// app/_layout.tsx
import { SpelycoProvider } from "@spelyco/react-native";
import { Stack } from "expo-router";

export default function RootLayout() {
  return (
    <SpelycoProvider>
      <Stack screenOptions={{ headerShown: false }} />
    </SpelycoProvider>
  );
}

Pass a partial theme to override anything in DEFAULT_THEME, and defaultColorScheme to pin the first-launch preference (defaults to "auto").

<SpelycoProvider
  theme={{ primaryColor: "brand", defaultRadius: "lg" }}
  defaultColorScheme="dark"
>
  <Stack />
</SpelycoProvider>

3. New Architecture

Unistyles 3 requires React Native's New Architecture. Make sure your app.json (Expo) has:

{ "expo": { "newArchEnabled": true } }

Components

Style props

Box, Text, Button, and ActionIcon accept the full Mantine-compatible style-prop surface. Prop names and token resolution match Mantine 1:1.

<Box mt="md" px="lg" bg="blue.5" w="100%" />
<Text c="red.6" fz="lg" fw="600" ta="center" />
  • Spacing (m*, p*, top/left/bottom/right, inset) — size tokens ("md"), negative tokens ("-md"), or raw numbers/percentages.
  • Color (bg, c) — palette tokens ("blue.5", "blue") or raw colors.
  • Typographyfz (size token or number), fw, lts, ta, lh, fs, tt, td, ff, opacity.
  • Sizing / layout / positionw/miw/maw/h/mih/mah, pos, display, flex.
  • bd — object-based on RN (a partial border style), not a CSS string.

Mantine's background-image props (bgsz/bgp/bgr/bga) are not supported — React Native has no background image. Responsive object syntax (w={{ base, sm }}) is not supported either.

Box

A View primitive that accepts all style props (see above).

import { Box } from "@spelyco/react-native";

<Box p="md" gap="sm" bg="#f4f4f5">
  {/* children */}
</Box>

| Prop | Type | Description | | --- | --- | --- | | style props | see Style props | Full Mantine style-prop surface | | gap | number \| string | Flex gap (Box convenience, not a Mantine prop) | | ...rest | ViewProps | Any RN View prop |

Text

A Text primitive with size/weight tokens. Color auto-resolves from the active color scheme unless you provide one.

import { Text } from "@spelyco/react-native";

<Text size="xl" weight="bold">Heading</Text>
<Text size="sm">Body copy</Text>
<Text c="red.6">Inline override</Text>

| Prop | Type | Default | Description | | --- | --- | --- | --- | | size | "xs" \| "sm" \| "md" \| "lg" \| "xl" | "md" | Maps to theme.fontSizes | | weight | "normal" \| "medium" \| "bold" | "normal" | | | c | string | scheme-aware default | Text color (palette token or raw) | | color | string | — | Deprecated — alias of c, will be removed in a future major | | style props | see Style props | — | Full Mantine style-prop surface | | ...rest | TextProps | — | Any RN Text prop |

Button

import { Button } from "@spelyco/react-native";

<Button label="Primary" variant="primary" />
<Button label="Secondary" variant="secondary" />
<Button label="Ghost" variant="ghost" />
<Button label="Small" size="sm" />
<Button label="Disabled" disabled />
<Button label="Save" onPress={() => console.log("saved")} />

| Prop | Type | Default | Description | | --- | --- | --- | --- | | label | string | required | Button text | | variant | "primary" \| "secondary" \| "ghost" | "primary" | Visual style | | size | "sm" \| "md" \| "lg" | "md" | Padding/font size | | disabled | boolean | false | Disable the button | | ...rest | PressableProps | — | Any RN Pressable prop |

Button and ActionIcon play a built-in press animation (scale + opacity) via usePressAnimation. It is skipped automatically while disabled.

Field system

The shared skeleton for form components. Field stacks a label, description, control, and error vertically; FieldControl is the bordered interactive box; FieldInline lays out toggle-style controls horizontally.

import { Field, FieldControl, FieldInline } from "@spelyco/react-native";

<Field label="Email" description="We never share it." error={errorText} withAsterisk>
  <FieldControl size="md" focused={focused} hasError={!!errorText}>
    {/* a TextInput, Select trigger, ... */}
  </FieldControl>
</Field>

<FieldInline label="Notifications" labelPosition="right">
  {/* a Switch, Checkbox, ... */}
</FieldInline>

| Component | Purpose | | --- | --- | | Field | Vertical label / description / control / error layout | | FieldControl | Bordered box with leftSection/rightSection, size, and focused/hasError/disabled states | | FieldInline | Horizontal layout for toggle controls (labelPosition: "left" \| "right") | | FieldLabel / FieldDescription / FieldError | The individual parts |

FieldBaseProps (label, description, error, withAsterisk, disabled, size) is the shared prop set form components extend.

Text fields

TextInput, PasswordInput, Textarea, and NumberInput are built on the Field system. They all accept FieldBaseProps, the native TextInput props, and the Box style props, and support controlled and uncontrolled usage.

import { TextInput, PasswordInput, Textarea, NumberInput } from "@spelyco/react-native";

<TextInput label="Email" placeholder="[email protected]" withAsterisk />

<PasswordInput
  label="Password"
  description="At least 8 characters."
  onVisibilityChange={(visible) => console.log(visible)}
/>

<Textarea label="Bio" autosize minRows={3} maxRows={8} />

<NumberInput
  label="Quantity"
  min={0}
  max={99}
  step={1}
  defaultValue={1}
  onChange={(value) => console.log(value)}
/>

| Component | Extra props | | --- | --- | | TextInput | leftSection / rightSection, plus all FieldBaseProps and native TextInput props | | PasswordInput | visible / defaultVisible / onVisibilityChange for the show/hide toggle | | Textarea | autosize, minRows (default 3), maxRows | | NumberInput | min / max / step, withControls, and a numeric value / defaultValue / onChange |

PasswordInput and NumberInput require the @tabler/icons-react-native and react-native-svg peer dependencies for their section icons.

Toggle controls

Switch, Checkbox, and Radio are toggle-style controls built on the FieldInline layout. They all accept FieldBaseProps, the native Pressable props, the Box style props, a labelPosition ("left" | "right"), and support controlled (checked) and uncontrolled (defaultChecked) usage.

import { Switch, Checkbox, Radio } from "@spelyco/react-native";

<Switch label="Notifications" defaultChecked onChange={(on) => console.log(on)} />

<Checkbox label="Accept terms" withAsterisk />
<Checkbox label="Partial selection" indeterminate />

<Radio label="Standard plan" />

Checkbox.Group and Radio.Group wrap several items and own a shared selection — a values array for Checkbox.Group, a single value for Radio.Group. Each child needs a unique value.

<Checkbox.Group label="Toppings" defaultValue={["cheese"]} onChange={setToppings}>
  <Checkbox value="cheese" label="Cheese" />
  <Checkbox value="olives" label="Olives" />
</Checkbox.Group>

<Radio.Group label="Size" defaultValue="md" orientation="horizontal">
  <Radio value="sm" label="Small" />
  <Radio value="md" label="Medium" />
</Radio.Group>

| Component | Extra props | | --- | --- | | Switch | checked / defaultChecked / onChange, labelPosition, size | | Checkbox | checked / defaultChecked / onChange, indeterminate, value (inside a group), labelPosition, size | | Radio | checked / defaultChecked / onChange, value (inside a group), labelPosition, size | | Checkbox.Group | value / defaultValue (string array), onChange, disabled, orientation | | Radio.Group | value / defaultValue (string), onChange, disabled, orientation |

Switch animates its thumb and track colour via react-native-reanimated. Checkbox requires the @tabler/icons-react-native and react-native-svg peer dependencies for its tick icon.


Press animation

usePressAnimation drives a Reanimated scale + opacity feedback for any custom pressable.

import Animated from "react-native-reanimated";
import { usePressAnimation } from "@spelyco/react-native";

function PressableCard() {
  const { animatedStyle, onPressIn, onPressOut } = usePressAnimation();

  return (
    <Animated.Pressable
      onPressIn={onPressIn}
      onPressOut={onPressOut}
      style={[styles.card, animatedStyle]}
    />
  );
}

| Option | Type | Default | Description | | --- | --- | --- | --- | | scale | number | 0.97 | Scale applied while pressed | | activeOpacity | number | 0.6 | Opacity applied while pressed | | disabled | boolean | false | Skip the animation |


Color scheme

SpelycoProvider tracks the user preference (light/dark/auto) in a zustand store, persists it via AsyncStorage, and listens to OS appearance changes.

import { useSpelycoColorScheme } from "@spelyco/react-native";

function ThemeToggle() {
  const {
    colorScheme,           // user preference: 'light' | 'dark' | 'auto'
    computedColorScheme,   // resolved scheme actually applied: 'light' | 'dark'
    setColorScheme,
    toggleColorScheme,
    clearColorScheme,      // reset to 'auto'
  } = useSpelycoColorScheme();

  return (
    <Button
      label={`Switch to ${computedColorScheme === "dark" ? "light" : "dark"}`}
      onPress={toggleColorScheme}
    />
  );
}

Theming

Customize defaults via theme.components[ComponentName].defaultProps:

<SpelycoProvider
  theme={{
    components: {
      Button: Button.extend({ defaultProps: { variant: "ghost" } }),
    },
  }}
>
  {/* every <Button> now defaults to variant="ghost" */}
</SpelycoProvider>

Theme tokens (colors, spacing, radius, fontSizes, lineHeights, shadows, breakpoints, systemBars, other) all flow through the same theme prop. See @spelyco/react-lib for the full SpelycoTheme contract.


Peer dependencies

| Package | Version | | --- | --- | | react | >=18 | | react-native | >=0.76 | | react-native-unistyles | ^3.2.4 | | react-native-edge-to-edge | * | | react-native-nitro-modules | * | | react-native-reanimated | * | | @react-native-async-storage/async-storage | * | | expo-status-bar | * | | expo-system-ui | * |


License

MIT