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

@hulla/style-native

v1.0.0

Published

Typed React Native style composition and variants for the @hulla/style ecosystem

Readme

@hulla/style-native

Extend the existing style() factory from @hulla/style to compose React Native style objects and define typed variants. This optional plugin exports the native preset; the core owns cn, vn, and variant inference. The preset contains an identity serializer and a native composer for the existing configuration hooks. It has no runtime imports; React Native supplies its style types.

Requires @hulla/style 0.5.0 or later, before 0.6, and React Native 0.81 or later, before 1.0. Typed usage requires TypeScript 5.4 or later.

Install both packages in the native app:

pnpm add @hulla/style @hulla/style-native

The core does not depend on the native plugin, so web consumers install only @hulla/style. Configure the preset once in the app's shared native style module.

Compose a native style

Create one shared pair of helpers outside your components. The same pair handles view, text, and image recipes:

// src/lib/native-style.ts
import { style } from "@hulla/style"
import { native } from "@hulla/style-native"

export const { cn, vn } = style(native)

In a native component module, combine existing styles with conditional values. React Native applies later styles last, so caller overrides go at the end:

import { StyleSheet, View, type ViewProps } from "react-native"
import { cn } from "../lib/native-style"

const styles = StyleSheet.create({ base: { padding: 12 }, muted: { opacity: 0.5 } })

export function Surface({ muted = false, style, ...props }: ViewProps & { muted?: boolean }) {
  return <View {...props} style={cn(styles.base, muted && styles.muted, style)} />
}

<Surface muted style={{ padding: 8 }} /> keeps the muted opacity and overrides the base padding with 8. Nested arrays are passed through unchanged for React Native to resolve. cn does not flatten, clone, deduplicate, or merge style objects.

Select a named variant

Define each map once near its component. The core's shared vn passes the selected value through the same composition function as cn. With the native adapter, a selected style object or array retains its reference:

const $size = vn({
  compact: { padding: 8 },
  regular: { padding: 12 },
})

type Size = typeof $size.infer // "compact" | "regular"

const compact = $size("compact")
cn(compact, { paddingHorizontal: 16 })

Use JavaScript defaults such as size = "regular" for optional component props. vn does not choose defaults. .infer is a type helper whose runtime value is undefined, matching @hulla/style.

Share the helpers across native components

Import the same cn and vn for view, text, and image styles. Each call keeps its input style shape, and each variant key retains the selected value's type:

import { cn, vn } from "../lib/native-style"

const $weight = vn({ regular: { fontWeight: "400" }, strong: { fontWeight: "600" } })
const labelStyle = cn($weight("strong"), { fontSize: 14 }) // Text.style
const imageStyle = cn({ resizeMode: "cover" }) // Image.style

React Native checks the result against the component's own style prop. To validate a recipe at its declaration, use satisfies with the target native style type:

import type { ImageStyle, TextStyle, ViewStyle } from "react-native"
import { cn } from "../lib/native-style"

const containerStyle = cn({ padding: 16 } satisfies ViewStyle)
const titleStyle = cn({ fontSize: 20, fontWeight: "600" } satisfies TextStyle)
const photoStyle = cn({ resizeMode: "cover" } satisfies ImageStyle)

For a whole variant map, check each entry with Record<string, TextStyle>:

import type { TextStyle } from "react-native"
import { vn } from "../lib/native-style"

const $weight = vn({
  regular: { fontWeight: "400" },
  strong: { fontWeight: "600" },
} satisfies Record<string, TextStyle>)

type Weight = typeof $weight.infer // "regular" | "strong"

satisfies checks the properties without converting the runtime object or replacing the map's inferred keys. StyleSheet.create is another option for static recipes. Keep the same shared cn and vn across component types.

Class strings and class-condition maps are rejected by TypeScript. Keep the preset's identity serializer paired with its native composer; the default web serializer would convert objects to class strings.

Compose interactive and themed styles

React Native owns interaction callbacks. Resolve the pressed state first, then compose its style values:

import { Pressable, StyleSheet } from "react-native"
import { cn } from "../lib/native-style"

const styles = StyleSheet.create({ base: { padding: 12 }, pressed: { opacity: 0.7 } })

export function Action() {
  return <Pressable style={({ pressed }) => cn(styles.base, pressed && styles.pressed)} />
}

For @hulla/ui, configure NativeThemeProvider once and read useNativeTheme() where a custom style needs tokens. Pass the resulting styles through public style props, or through Button.contentStyle for its visible surface. The adapter does not need theme props, subscribe to themes, or change component sizing.

Reference and cost

| API | Result | | ---------------------- | ------------------------------------------------------------------------------------------------- | | native | A shared { serializer, composer } object passed to style(native) | | cn(...styles) | undefined for no active top-level inputs; the exact input for one; an ordered array for several | | vn(map)(key) | The selected value composed through cn | | typeof variant.infer | Union of the map's keys |

Falsy top-level inputs are omitted, including empty variant values; nested inputs retain their native meaning. vn({ hidden: false })("hidden") returns undefined. Objects and arrays returned from StyleSheet.create can be used directly. The adapter follows the installed React Native StyleProp<T> type, including style references on versions that expose them. It does not evaluate callback functions; call cn inside the callback instead.

Each cn call uses the existing core pipeline: it maps arguments through the identity serializer, then calls the native composer. The mapped argument array is new; style objects and nested arrays retain their references. Multiple active styles produce an ordered array. This does not promise zero allocations or faster rendering. Use stable style declarations where practical and measure representative screens before changing styling backends. NativeWind compilation, animations, gestures, theme subscriptions, and design tokens remain outside this package.