@pockets-ui/tokens-native
v3.4.0
Published
The pockets design tokens, resolved for React Native / Expo. Generated from the @pockets-ui/theme Panda preset; ships as react-native-unistyles themes.
Readme
@pockets-ui/tokens-native
The pockets design tokens, resolved for React Native and Expo.
The web side of pockets ships as a Panda CSS preset compiled to stylesheets — CSS
variables, selectors, rem. None of that exists in React Native. This package
carries the same tokens, generated from the same preset, as plain data:
dp numbers, hex colors, and the _dark condition flattened into a second theme
object.
It has no dependencies and imports nothing at runtime — not even react-native.
It is shaped for react-native-unistyles, but it is
just data, so StyleSheet.create works equally well.
Install
bun add @pockets-ui/tokens-nativeSetup with Unistyles
// unistyles.ts — imported once, before any component
import { StyleSheet } from "react-native-unistyles"
import { themes } from "@pockets-ui/tokens-native"
StyleSheet.configure({
themes,
settings: { adaptiveThemes: true }, // follow the OS light/dark setting
})
declare module "react-native-unistyles" {
export interface UnistylesThemes {
light: typeof themes.light
dark: typeof themes.dark
}
}import { StyleSheet } from "react-native-unistyles"
import { Text, View } from "react-native"
const styles = StyleSheet.create((theme) => ({
card: {
backgroundColor: theme.colors.bg.highlight,
padding: theme.spacing[4],
borderRadius: theme.radii.md,
...theme.borders.sm,
...theme.shadowsLegacy.sm,
},
title: {
...theme.textStyles.heading.section,
color: theme.colors.fg.DEFAULT,
},
}))
export const Card = () => (
<View style={styles.card}>
<Text style={styles.title}>Cash flow</Text>
</View>
)What's in a theme
| Key | Shape | Notes |
| --- | --- | --- |
| colors | nested strings | Primitive ramps (blue.600) and semantic intents (bg.action.inverse) in one namespace, as Panda keys them |
| spacing, sizes, radii, borderWidths, fontSizes | numbers (dp) | |
| borders | { borderWidth, borderColor } | The CSS shorthand split in two; spread it |
| fonts, fontWeights | strings | |
| lineHeights, letterSpacings | ratios / em | Multiply by a font size — RN wants absolute dp |
| textStyles | RN text styles | lineHeight already resolved to dp |
| textStylesMd | RN text styles | Only the display styles, at tablet sizes |
| shadows | boxShadow layers | Faithful, RN 0.76+ |
| shadowsLegacy | classic shadow props | Lossy — see below |
| durations, easings | ms / bezier points | easings feed Easing.bezier(...) |
| zIndex | numbers | |
Panda's DEFAULT key survives verbatim: colors.bg.DEFAULT is the base surface,
colors.bg.subtle a sibling. An object cannot be both a string and a container
the way a CSS variable name can, so the key stays explicit. resolveColor() is
exported for looking colors up by the dot paths the web recipes use, and applies
the same DEFAULT fallback Panda does.
Fonts
Nothing here loads a font. fonts.heading and fonts.body are the names your app
must register the font files under:
useFonts({
"Funnel Display": require("./assets/FunnelDisplay-SemiBold.ttf"),
"Instrument Sans": require("./assets/InstrumentSans-Regular.ttf"),
})Two native wrinkles with no web equivalent:
- Android ignores
fontWeighton a custom family. Each weight has to be registered as its own family ("Instrument Sans SemiBold") and selected by name. ThetextStyleshere carryfontWeight, which is correct on iOS; if you need Android weights, map the family name per weight in your app. - There is no fallback chain. RN takes one
fontFamilyand silently falls back to the system font if it is missing, so a typo shows up as "the type looks wrong" rather than an error.fonts.monois themonospacegeneric, which only Android recognises — usePlatform.select(MONO_FAMILY_BY_PLATFORM)for a real monospace on both.
The lossy conversions
Three conversions cannot be faithful, and are worth knowing about:
- Shadows. CSS
box-shadowstacks layers; iOS and Android take one shadow per view.shadowsis the honest RN 0.76+boxShadowform with every layer intact.shadowsLegacycollapses each token onto its first layer for the old architecture, halves the blur intoshadowRadius, and approximates an Androidelevation. Prefershadowsif you are on 0.76+ with the New Architecture. - OKLCH → hex. The ramps are authored in OKLCH at three decimal places, so
converting back to sRGB loses sub-step precision. It is under 2/255 everywhere
except the two highest-chroma brand anchors, where it reaches 6/255: vermilion
#FF4F01lands as#ff4f07and BP Blue#023AD7as#063ad7. This is the same value a browser computes from the same OKLCH, so native and web agree — it is the documented hex comment in the theme that is the outlier. - Responsive type. The three
displaystyles are authored with a mobile and a desktop size. RN has no media queries, so the mobile size is intextStylesand the desktop size intextStylesMd; wire the switch to a Unistyles breakpoint if you support tablets.
sizes also drops min, max, fit and prose — min-content, fit-content
and 65ch mean nothing to a native layout engine.
Regenerating
src/tokens.generated.ts is checked in, so token changes are reviewable as a
diff and the package needs no build step to install.
bun run generateThe generator imports the very same preset.theme.extend object Panda consumes
for the web build — that is what keeps the two sides from drifting. A test
re-runs the generator and fails if the checked-in output changes, so a token
retuned in @pockets-ui/theme cannot silently go missing on native. Each
conversion is documented at its build* function in scripts/generate.ts.
Not included
Recipes. The component recipes in @pockets-ui/theme are authored against
web semantics — display: inline-flex, cursor, outline, CSS transitions, and
&[data-hovered] selectors driven by react-aria-components' state model. Porting
them needs a resolver that maps Panda style props to RN style props and turns
those state selectors into explicit props, which is a separate piece of work.
Components. @pockets-ui/components is built on react-aria-components and
react-dom; there is no native equivalent to reuse.
