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

pixelize-native-library

v2.15.0

Published

Pixelize mobile design library — React Native / Expo. The mobile counterpart to pixelize-design-library.

Readme

pixelize-native-library

Pixelize's React Native / Expo design library (Pebble 2.0) — the mobile counterpart to pixelize-design-library (web). Nine brands, light and dark, corner and density controls, accessible in every state.

Building a screen? Start at DESIGN-GUIDE.md section 10: the screen-kind to reference-screen table.

Where to look (all of it ships in the package)

| Need | File in node_modules/pixelize-native-library/ | | --- | --- | | Every module, its exact import subpath, one line | registry.json | | One module's props, behaviour and gotchas | lib/module/modules/<name>/MODULE.md (core: lib/module/core/<name>/MODULE.md); prop JSDoc shows in your editor via the .d.ts | | Composing a screen that looks designed | DESIGN-GUIDE.md | | Traps that compile and are wrong at runtime | COMPONENT-GOTCHAS.md | | Keeping an app fast, smooth and small: the 10 rules, dates and locale/strings, the pinned native stack and its guard, the phone checks | CONSUMER-PERFORMANCE.md | | Motion, reduced motion, low-end devices, and the app-side settings that make a screen fast (inlineRequires, stable handlers, loading props) | MOTION-AND-PERFORMANCE.md | | Known gaps and bugs, with status | GAPS.md | | Upgrading from 1.x | MIGRATION.md | | Every release | CHANGELOG.md |

A hosted playground, https://mobilecomp.pixelizetech.com, shows every component live (/#/c/<id>, add ?brand=zinc&mode=dark&corner=round&density=default) and mirrors these docs as text: /llms.txt, /components/<id>.md, /screens/<id>.md and /gaps.md. It tracks the newest deploy, so for an older pinned version read the shipped files above first. With this repo checked out, npm run web runs the same playground plus 23 reference screens (example/screens/examples; the screen-kind table is DESIGN-GUIDE.md section 10).

Getting started

New to this library? Follow these in order. Coming from 1.x: read MIGRATION.md first.

Setup checklist

  1. Runtime. Expo SDK 57+ (the example runs Expo 57 on React Native 0.86) or bare React Native 0.82+. New Architecture only (Reanimated 4 has no old-architecture build) and Hermes. Expo Go carries Reanimated 4.5.1 and worklets 0.10.1; an app on any other pair (the Pixelize apps pin 4.7.1 and 0.13.0) cannot open in it, because worklets compares its JS and native versions at start. Use a dev build, and see CONSUMER-PERFORMANCE.md for the pins and the guard.
  2. Peers. Install the required ones and the optional ones you use, below, then rebuild the native app.
  3. Babel. Reanimated 4 moved its plugin to react-native-worklets/plugin, and it must be the last entry in plugins. babel-preset-expo adds it itself when react-native-worklets is installed, so an Expo app needs no entry (react-native-reanimated/plugin is only an alias of it in 4.x). Bare React Native: add it, last.
  4. Metro. Subpaths such as pixelize-native-library/data-table resolve through package exports. Metro enables that by default in the versions this library targets (Expo 57, React Native 0.86), so the setting is redundant there; on an older bare setup set config.resolver.unstable_enablePackageExports = true.
  5. TypeScript. "moduleResolution": "bundler" (Expo's expo/tsconfig.base sets it). node16 and nodenext also work; classic node cannot see subpath types.
  6. Root wrappers, in this order: GestureHandlerRootView (style={{ flex: 1 }}) > SafeAreaProvider > PixelizeProvider > your navigator. Swipe, drag, slider and week-strip gestures need the first; insets are guesses without the second.
  7. Fonts. Load Plus Jakarta Sans (or Inter) before the first render; see Typography.
  8. Injectors. Copy pebble.setup.ts (below) and import it first in your entry file.
  9. Jest. Add the lucide mapper from "Testing your app with jest".
  10. Imports. Heavy modules and the core surfaces are subpath-only; see "Finding a component".

Install

npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler react-native-svg \
  react-native-safe-area-context expo-font @expo-google-fonts/plus-jakarta-sans
npm i pixelize-native-library lucide-react-native@^1.47.0

Add the optional packages you use with npx expo install <name> (npm i for the two non-Expo ones).

| Package | Floor | Status | Used for | Native rebuild | | --- | --- | --- | --- | --- | | react, react-native | 19.0, 0.82 | required | everything | n/a | | react-native-reanimated, react-native-worklets | 4.5 to 4.7 (<4.8), 0.10 to 0.13 (<0.14) | required | all motion | yes | | react-native-gesture-handler | 2.32 (^2.32.0; 3.x not checked) | required | swipe, drag, sliders, sheets | yes | | lucide-react-native | 1.47 | required | every icon | no | | react-native-svg | 15.15 (<16) | required | icons, charts, rings | yes | | react-native-safe-area-context | 5.0 | optional, install it | real insets, overlay insets on Android | yes | | @react-native-async-storage/async-storage | 2.0 | optional | PixelizeProvider storage (persist theme) | yes | | @shopify/flash-list | 2.0 | optional | VirtualList and ListScreen use it when present | no | | expo-blur | 57 | optional | glass surfaces (AppBar, BottomNav, SaveBar) | yes | | expo-haptics | 57 | optional | haptic feedback | yes | | expo-device | 57 | optional | Android phones with 4 GB of RAM or less get the low-end motion path | yes | | expo-image | 57 | optional | MediaImage, Avatar images | yes | | expo-image-picker, expo-document-picker | 57 | optional | FileUpload, AvatarUpload | yes | | expo-video, expo-audio | 57 | optional | MediaViewer playback | yes | | expo-sharing, expo-file-system | 57 | optional | data-export CSV / .xlsx share sheet | yes | | react-native-view-shot | 4.0 | optional | theme crossfade | yes | | @react-native-clipboard/clipboard or expo-clipboard | 1.14 / 57 | optional | CopyButton on native; expo-clipboard through setClipboardModule | yes | | react-native-webview | 13.0 | optional | PdfDocumentRenderer, the PDF pages in DocumentViewer and MediaViewer | yes | | react-native-enriched-html | 1.0 | optional | rich-text-editor (a dev build, not Expo Go) | yes | | react-dom | 19.0 | optional | React Native Web only | no |

Why lucide must be 1.47 or newer: every icon is a deep import (lucide-react-native/icons/<glyph>) that Metro resolves at bundle time, even for icons you never render. Three glyphs do not exist before 1.47, so an older copy fails the whole bundle with Unable to resolve module lucide-react-native/icons/... on any screen that imports Button, Select or Icon. Check npm ls lucide-react-native: a lockfile can pin an old one.

Typography

The library ships no font — without one it falls back to the system font and the provider logs a dev warning. Plus Jakarta Sans is the Pebble face; Inter is supported too.

import { useFonts } from "expo-font";
import {
  PlusJakartaSans_400Regular,
  PlusJakartaSans_500Medium,
  PlusJakartaSans_600SemiBold,
  PlusJakartaSans_700Bold,
  PlusJakartaSans_800ExtraBold,
} from "@expo-google-fonts/plus-jakarta-sans";

const [fontsLoaded] = useFonts({
  "PlusJakartaSans-Regular": PlusJakartaSans_400Regular,
  "PlusJakartaSans-Medium": PlusJakartaSans_500Medium,
  "PlusJakartaSans-SemiBold": PlusJakartaSans_600SemiBold,
  "PlusJakartaSans-Bold": PlusJakartaSans_700Bold,
  "PlusJakartaSans-ExtraBold": PlusJakartaSans_800ExtraBold,
});

Pass fonts="jakarta" to PixelizeProvider once fontsLoaded is true (below). fonts="inter" expects the faces Inter-Regular, Inter-Medium, Inter-SemiBold, Inter-Bold and Inter-ExtraBold instead; fonts also takes a { 400: "MyFace-Regular", … } map, and a weight you leave out falls back to the nearest registered face, then the system font. interLoaded is a deprecated alias of fonts="inter".

The provider

Wrap the app root once, after fonts finish loading, inside the two root wrappers from the checklist:

import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider } from "react-native-safe-area-context";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { PixelizeProvider, createAsyncStorageAdapter } from "pixelize-native-library/theme";
import { Button } from "pixelize-native-library";

export default function App() {
  // const [fontsLoaded] = useFonts({ ... }); if (!fontsLoaded) return null;

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <SafeAreaProvider>
        <PixelizeProvider
          brand="zinc"
          mode="system"
          fonts="jakarta"
          density="compact"
          storage={createAsyncStorageAdapter(AsyncStorage)}
        >
          <Button label="Save" onPress={save} />
        </PixelizeProvider>
      </SafeAreaProvider>
    </GestureHandlerRootView>
  );
}

Start every new app at density="compact". A phone is small: compact uses its full width and height (12 dp gutter, 12 dp between blocks, about one more row per screen) and shrinks no text and no 44 dp touch target. The library's own default is default, so you must ask for it. Build screens compact from day one: read every gutter and gap from usePebble().layout and never write a 16 or 24 for padding, margin or gap (DESIGN-GUIDE.md section 2a, with a block to paste into your app's CLAUDE.md).

PixelizeProvider props are all optional: brand (zinc default), mode (light | dark | system, default system), corner (sharp | soft | round, default round), density (compact | cozy | default | comfortable, default default; use compact in new apps, cozy as the middle step), fontScale (0.8 to 1.5, default 1), fonts, accent, palette, storage, haptics, statusBar (default on), themeTransition, locale (the default for every date picker's month and weekday names), strings (your own words for the date pickers' Cancel, Done, placeholders, presets and spoken labels; keys you leave out stay English). Details: lib/module/theme/MODULE.md.

storage persists mode, brand, corner, density and text scale across restarts; it needs @react-native-async-storage/async-storage installed, and createAsyncStorageAdapter wraps it so the library itself never imports that package directly. Without storage the provider still works, but keeps the choice in memory only (lost on restart) and logs a dev warning saying so.

Startup file: pebble.setup.ts

Optional native modules are handed to the library once, so nothing imports them at module scope. Import this file first in your entry file (import "./pebble.setup" above registerRootComponent) and delete the lines for packages you did not install: Metro fails the bundle on a require of a package that is missing.

import { Platform } from "react-native";
import { Image } from "expo-image";
import * as DocumentPicker from "expo-document-picker";
import * as ImagePicker from "expo-image-picker";
import {
  setAudioModule,
  setDocumentPicker,
  setExpoImage,
  setImagePicker,
  setVideoModule,
} from "pixelize-native-library/media";
import { setHapticsModule } from "pixelize-native-library/haptics";
import { setBlurModule } from "pixelize-native-library/blur";
import { setViewShotModule } from "pixelize-native-library/theme";
import { setFileSystemModule, setSharingModule } from "pixelize-native-library/data-export";
import { setClipboardModule } from "pixelize-native-library/copy-button";
import { PdfDocumentRenderer, setDocumentRenderer } from "pixelize-native-library/document-viewer";

setExpoImage(Image as never);
setImagePicker(ImagePicker);
setDocumentPicker(DocumentPicker);
setVideoModule(require("expo-video"));
setAudioModule(require("expo-audio"));
setSharingModule(require("expo-sharing"));
setFileSystemModule(require("expo-file-system"));
setClipboardModule(require("expo-clipboard"));
setHapticsModule(require("expo-haptics"));
setBlurModule(require("expo-blur").BlurView);
if (Platform.OS !== "web") setViewShotModule(require("react-native-view-shot"));
setDocumentRenderer(PdfDocumentRenderer);

| Setter | Import from | Without it | | --- | --- | --- | | setImagePicker, setDocumentPicker | /media (also /file-upload and the root) | FileUpload, AvatarUpload throw on pick | | setExpoImage | /media | MediaImage falls back to React Native Image | | setVideoModule, setAudioModule | /media | MediaViewer shows an "is not set up" panel for video and audio | | setSharingModule, setFileSystemModule | /data-export (also the root) | shareTabularData, shareTextFile return unavailable; they never throw | | setClipboardModule | /copy-button (also the root) | on native, CopyButton reports failure unless @react-native-clipboard/clipboard is installed | | setHapticsModule | /haptics | no haptics | | setBlurModule | /blur | glass surfaces render an opaque fill instead of a blur | | setViewShotModule | /theme | theme change is instant, no crossfade | | setDocumentRenderer | /document-viewer (also the root) | DocumentViewer offers "Open in browser"; MediaViewer PDFs get an Open, Share and Download panel. PdfDocumentRenderer needs react-native-webview and reaches cdnjs for pdf.js |

Bring your own brand colour

A consumer app that wants its own brand colour does not need to add a new brand folder under this library's src/theme/tokens/brands/. Pass a single hex to accent:

<PixelizeProvider brand="zinc" accent="#3a75cf" mode="system">
  {/* your app */}
</PixelizeProvider>

accent is turned into a full, contrast-checked PaletteProps by paletteFromAccent(accentHex, brand, resolvedMode) (pixelize-native-library/tokens). It takes the brand you passed as a structural template — that brand's neutral/gray scale, status colours, semantic text and surfaces are reused unchanged — and rebuilds only the primary ramp (and the two-stop gradient derived from it) from your hex. So brand still matters with accent set: it picks the neutral/status base the rest of the UI is built from, and only the accent ramp changes. Any hex is safe: the resulting fill is checked against that base (3:1 as a mark on bg, surface and raised; label ink 4.5:1 at rest and pressed), so it clears the same gates every shipped brand does, exercised for 73 accents x 9 brands x both modes in accentStress.test.ts. A hex that already passes is used byte-for-byte (#3a75cf never moves); one that cannot (near-black in dark, near-white in light, mid-greys, some pure blues and magentas in dark) is moved in lightness only, same hue, so the same hex can render slightly lighter in dark than in light. Accepted input is #RGB or #RRGGBB (uppercase and a missing # are fine); anything else throws a clear error at mount. Because light and dark need different structural bases, the provider re-derives the palette itself on every mode="system" flip — you don't have to.

For more control than one hex gives (a hand-tuned ramp, a different gradient, a bespoke status colour), pass a fully-authored palette prop instead — a PaletteProps object, which wins over both brand and accent. A common starting point is paletteFromAccent's own output, then override individual fields. Unlike accent, palette carries no mode awareness of its own — it is a static object, so rebuild it yourself whenever resolvedMode changes (read it from useThemeMode(), see below):

import { paletteFromAccent } from "pixelize-native-library/tokens";

<PixelizeProvider palette={paletteFromAccent("#3a75cf", "zinc", resolvedMode)}>
  {/* your app */}
</PixelizeProvider>;

Most apps only need accent; reach for palette only when accent alone can't express what you need.

Finding a component

Most components come from the root barrel:

import { Button, Card, Screen, Text } from "pixelize-native-library";

Not on the root barrel (a root import fails to resolve; use pixelize-native-library/<name>):

  • the 19 heavy modules bottom-nav, charts, coachmark, context-menu, data-table, date-picker, filter-sheet, gantt, image-viewer, kanban-board, media-viewer, module-bar, onboarding-pager, phone-input, progress-card, reorder, rich-text, rich-text-editor and widget-board (the HEAVY_MODULES set in scripts/gen-index.mjs);
  • the deprecated state-block alias (LoadingState and ErrorState are on the root);
  • the core surfaces icon (Icon), tile (IconTile), blur (GlassSurface), media (MediaImage), haptics (useHaptics), theme, tokens and style. The root carries only the injector setters of haptics, blur and media. motion is on the root too.

The root barrel pulls every light module: importing a Button from it bundled about +1.5 MB minified JS against about +448 KB from pixelize-native-library/button (measured, GAP-095). Import by subpath in an app you ship; keep the root for prototypes. This is not tree-shaking; Metro cannot do that for a library.

A Fab or FabGroup above the floating BottomNav takes offset={useBottomNavFabOffset()} (from pixelize-native-library/bottom-nav); never derive it from useBottomNavInset(), which already contains the safe inset the Fab adds itself.

Before building something by hand, find it here, then read its MODULE.md. Every name imports as pixelize-native-library/<module>; a dagger marks a subpath-only module. registry.json has the full list.

Which component for which job

| Job | Modules (main exports) | | --- | --- | | Text fields | input (Input, PasswordInput), textarea, number-input (NumberInput, CurrencyInput), pin-input, otp-code-step, search-bar, rich-text-editor†, wrapped in form-field | | Pickers (draw their own label; never in form-field) | select, multi-select, combobox, tag-input, phone-input†, date-picker† (DatePicker, DateRangePicker, TimePicker, Calendar, WeekStrip), hrms-month-calendar | | Choice and input controls | checkbox, radio-group, switch, segmented-control, chip, choice-card, slider, rating, quantity-stepper, file-upload, avatar-upload, save-bar | | Actions | button (Button, IconButton, ToggleButton), button-group, fab, link, social-button, copy-button, upgrade-button | | Lists and rows | list-item, list-screen, section-list (SectionList, IndexBar), card-group, setting-row, settings-section, key-value, description-list, payslip-line-list, swipeable-row, reorder†, data-table† (DataTable, CardTable), accordion, timeline, pagination, more-items, file-grid, folder-row | | Navigation and screens | screen, auth-layout, app-bar, bottom-nav†, module-bar†, tabs, breadcrumbs, stepper, stage-progress, pager-dots, carousel, onboarding-pager†, motion (StackTransition, FadeSwitch), drawer, app-switcher, org-switcher, project-switcher, project-tile, filter-bar, filter-sheet† (FilterSheet, SortSheet, FilterChip), field-select-modal | | Feedback and state | toast, banner, empty-state, error-state, loading-state, result-state, skeleton, spinner, progress-bar, progress-ring, progress-card†, pull-to-refresh, tooltip, coachmark†, notification-bell, count-badge, unread-badge, status-dot, launch-hold, refresh-pill | | Overlays | modal, alert-dialog, bottom-sheet, action-sheet, menu, context-menu†, popover, drawer, backdrop, limit-reached-modal | | Data display | card, surface, text (Text, Heading), section, divider, amount, stat-tile, stat-grid, stat-chip-row, widget-board†, badge, tag, avatar, avatar-group, person-chip, attachment-tile, quick-actions, plan-badge, plan-pill, appearance-settings | | Domain cards | product-card, property-card, ticket-card, event-card, profile-card, review-card, balance-card, plan-card, payment-card, promo-card, notification-card, action-card, proposal-card, approval-queue-card, kanban-card, signed-in-card | | Charts and timelines | charts† (BarChart, LineChart, AreaChart, ScatterChart, PieChart, DonutChart, Sparkline, BarList, Gauge, FunnelChart), gantt†, kanban-board† | | Media and documents | media-viewer† (photo, video, audio, PDF, text, files), image-viewer†, document-viewer, markdown, rich-text†, voice-note, media (MediaImage), icon (Icon), tile (IconTile) | | Chat | message-bubble, composer, comment-thread, reaction-bar, date-separator, typing-indicator, voice-note | | Theme and utilities | theme, tokens, style (sx, layout types), haptics, blur, data-export (shareTabularData, shareTextFile), date-format (formatDisplayDate, formatDisplayDateTime, formatDisplayDateRange) |

Each prop carries a one-line JSDoc, so hover or go-to-definition on the import answers most "what does this prop do" questions. The longer version is each module's MODULE.md, under lib/module/modules/<name>/.

Dark mode and mode switching

mode on the provider is only the initial value. Read and change it at runtime with useThemeMode(), called from anywhere inside the provider:

import { useThemeMode } from "pixelize-native-library/theme";
import { Button } from "pixelize-native-library";

function AppearanceToggle() {
  const { resolvedMode, setMode } = useThemeMode();
  return (
    <Button
      label={resolvedMode === "dark" ? "Switch to light" : "Switch to dark"}
      onPress={() => setMode(resolvedMode === "dark" ? "light" : "dark")}
    />
  );
}

useThemeMode() also returns mode (the raw, unresolved setting), brand and setBrand, plus corner/setCorner, density/setDensity and fontScale/setFontScale (an in-app text size, 0.8 to 1.5, on top of the OS one). resolvedMode is mode with 'system' already resolved against the OS setting, and updates live if the OS setting changes under a mode="system" app. The setters write through the storage adapter passed to the provider, so the choice survives a restart — with no adapter it survives only until the app is closed. There is no shipped ThemeSwitcher component or a toggle() helper; build the control your app needs from mode / resolvedMode / setMode as above.

Testing your app with jest

Icon deep-imports lucide's ESM .mjs glyphs, which jest does not transform, so any screen test that renders an Icon fails with Cannot use import statement outside a module. Map the deep imports to lucide's CommonJS build and let jest transform the library itself (jest.config.js):

module.exports = {
  preset: "jest-expo",
  transformIgnorePatterns: [
    "node_modules/(?!((jest-)?react-native|@react-native|expo(nent)?|@expo|pixelize-native-library|lucide-react-native)/)",
  ],
  moduleNameMapper: {
    "^lucide-react-native/icons/(.*)$": "<rootDir>/node_modules/lucide-react-native/dist/cjs/icons/$1.js",
  },
};

Glyphs then render as real react-native-svg nodes. To skip drawing them, mock the icon subpath in your setup file instead (Icon is the only export that needs replacing):

jest.mock("pixelize-native-library/icon", () => ({
  ...jest.requireActual("pixelize-native-library/icon"),
  Icon: () => null,
}));

Reporting a gap or bug

  1. Check first. Read GAPS.md, the component's MODULE.md and COMPONENT-GOTCHAS.md. Many "bugs" are listed there, some already fixed in <version>.
  2. Reproduce on the playground page for that component (npm run web in this repo, or the hosted /#/c/<id>). It reproduces there: a library bug. It does not: it is your usage or theme setup.
  3. File it with gh issue create --repo pixelize-tech/Mobile-Components --title "[gap] <component>: <one line>" --body-file report.md, the body laid out as in GAPS.md ("Report format"; the human web form is the consumer-gap issue template). No gh access: print the filled body for a human to file. Always include the library version, brand, mode, corner, density and device.
  4. Track it in your own repo's notes with the issue link, and on every upgrade re-read GAPS.md and the changelog for fixed in <version> before you rely on a workaround.
  5. Maintainers flip the row in GAPS.md to fixed in <version> in the same PR that fixes it.

Why subpath imports

pixelize-native-library/button pulls Button's module subtree and nothing else; the root barrel pulls all of the light modules (about 3x the bytes, see "Finding a component"). Heavy modules are not on the root at all.

This is not tree-shaking. Metro's tree-shaking is experimental, production-only and enabled by a flag the consumer app sets; a library cannot turn it on. Package Exports work in dev and prod, on bare RN and Expo, with no config in your app. Pair it with Metro's inlineRequires so the modules you do import evaluate on first use rather than at startup; the other app-side speed settings are in MOTION-AND-PERFORMANCE.md and CONSUMER-PERFORMANCE.md.

Docs

The consumer docs are the files in "Where to look" above; all ship in the package. Repo only, not shipped:

  • Contributing and the API contract: CLAUDE.md
  • The performance checklist, gates and phone checks: PERFORMANCE-PLAYBOOK.md; native versions and the guard: docs/NATIVE-STACK.md
  • Playground and reference screens: example/README.md

Theme

Colour is resolved from one Pebble palette per brand and mode; useThemeColors() keeps the 1.4 shape and is derived from it, while new code reads usePebbleColors(). The token model is in lib/module/theme/tokens/THEME-CONTRACT.md. Font registration, the accent/palette props and mode switching are covered in Getting started above.

Web gotcha

Reanimated.Text typechecks but is undefined at runtime on web. Module code uses AnimatedText from core/motion, never Reanimated.Text.