Skip to content

@gj-kit/expo-ui

React Native and web primitives: an unnamed IconButton, Tabs, or Slider is a compile error.

  • npm 0.8.5
  • 0 runtime dependencies
  • 332 @ts-expect-error guards
  • 850+ unit tests
  • 58 components, 31 color roles

In a React Native design system the failures are silent: an IconButton ships with no accessibility label, a Tabs value is typo’d so the panel renders blank, an EmptyState action renders a button whose onPress was never wired, and a hand-assembled theme object leaks undefined into a style prop. None of that fails the build — it fails on a screen reader, in production, on someone else’s device.

  • Measured Android popup coordinates — Android anchored Menu and Select measure root-relative trigger page coordinates to match the full-window Modal. Status-bar insets are not hardcoded; iOS and web retain their existing placement paths.
  • Accessible names the type demands — IconButton without accessibilityLabel, a rich-children Button with no name, and a range Slider given one label instead of a two-thumb tuple are all rejected.
  • Dead buttons do not compile — ButtonInteractionProps is a union: onPress is required unless disabled or loading is literally true, and an EmptyState action must carry both label and onPress.
  • Tabs cannot lose a panel — panels is typed Readonly<Record<NoInfer<ItemValue>, NonNullable<ReactNode>>> and value is NoInfer-wrapped, so a typo’d value, a missing panel, and a null panel all fail typecheck.
  • createTheme is the only door — UiProvider’s theme prop accepts only a branded Theme or ThemePair produced by createTheme or createThemes; a hand-assembled token object is a compile error.
  • No design literals in the source — A guard test walks every .tsx file under src/components recursively and fails on any quoted hex color, any numeric fontSize, or any quoted numeric fontWeight.

Outcome: A themed, accessible button rendered from one application-wide provider.

Terminal window
pnpm add @gj-kit/expo-ui

Create themes once and mount UiProvider at the component that wraps your app. Native Button, IconButton, Menu and Select own their press feedback through native styles; their default interactions do not require NativeWind.

Copy this first, then replace only the app-owned values named above.

import { Button, UiProvider, enStrings } from '@gj-kit/expo-ui';
import { createThemes } from '@gj-kit/expo-ui/theme';
const themes = createThemes();
export function App() {
return (
<UiProvider theme={themes} strings={enStrings}>
<Button label="Get started" onPress={() => {}} />
</UiProvider>
);
}

Tabs requires the tablist’s accessible name and one non-null panel per item value, so a forgotten panel stops the build instead of rendering blank.

import { Tabs, Text, UiProvider, createThemes } from '@gj-kit/expo-ui';
declare const onChange: (value: 'overview' | 'history') => void; // the app owns tab state
const items = [{ label: 'Overview', value: 'overview' }, { label: 'History', value: 'history' }] as const;
export const ProfileTabs = () => (
<UiProvider theme={createThemes({ light: { colors: { primary: '#1769C2' } } })}>
<Tabs
accessibilityLabel="Profile sections"
items={items}
value="overview"
onChange={onChange}
// Delete the history entry below and tsc stops the build:
// TS2741: Property 'history' is missing in type '{ overview: JSX.Element; }'
panels={{ overview: <Text>Overview</Text>, history: <Text>History</Text> }}
/>
</UiProvider>
);

Use it when one design language, controlled component state, overlays, and accessibility behavior must work across native and web targets.

Do not use it for product routes, data stores, analytics, or product-specific copy.

Engines: node >=20

Peer Supported range Required for
react >=18 the package integration
react-native >=0.79 the package integration
react-native-safe-area-context >=4 documented optional subpaths only
react-native-web >=0.21 documented optional subpaths only
Entry Exported symbols
Root entry 361
./insets 11
./insets/pure 6
./tailwind 4
./theme 26

Keep optional safe-area and React Native Web peers behind their documented subpaths. Supply application copy through strings rather than baking product copy into primitives.

complete API reference contains every public function, class, type, constant, and error contract generated from release declarations. Each item shows its import path, release signature, and package version.

Relationship Package Why combine them
Standalone — This package can be adopted without another GJ Kit package.
Optional companion @gj-kit/expo-media Render media selection and upload state with accessible primitives.
Optional companion @gj-kit/expo-auth Show application-owned session state without coupling either package.

npm · GitHub