Skip to content

@askable-ui/react-native

React Native bindings for askable-ui.

This initial slice focuses on explicit mobile interactions: useAskable() provides a context backed by @askable-ui/core, useAskableScreen() lets you mirror screen focus into that context, useAskableScrollView() mirrors raw ScrollView measurement into that context, useAskableVisibility() mirrors viewability updates from mobile lists, and <Askable /> turns onPress / onLongPress interactions into prompt-ready focus updates.

A runnable Expo reference app lives in examples/react-native-expo.

Install

bash
npm install @askable-ui/react-native @askable-ui/core

<Askable>

Clones a single React Native pressable child and merges focus updates into its onPress and onLongPress handlers. Nested <Askable> wrappers also contribute ancestor segments, so press-driven mobile flows can serialize the same hierarchy paths as DOM-based web flows.

tsx
import { Pressable, Text } from 'react-native';
import { Askable, useAskable } from '@askable-ui/react-native';

function RevenueCard() {
  const { ctx } = useAskable();

  return (
    <Askable ctx={ctx} meta={{ widget: 'revenue' }} text="Revenue card">
      <Pressable>
        <Text>Revenue</Text>
      </Pressable>
    </Askable>
  );
}

Props:

PropTypeDescription
ctxAskableContextContext instance that receives focus updates
metaRecord<string, unknown> | stringMetadata pushed into the context on press
scopestringOptional category stored with press-driven focus for scoped prompt/history queries
textstringOptional human-readable label stored alongside meta
childrenReactElementA single child that accepts onPress / onLongPress props
tsx
<Askable ctx={ctx} meta={{ view: 'dashboard' }} text="Dashboard" scope="analytics">
  <Askable ctx={ctx} meta={{ tab: 'finance' }} text="Finance" scope="analytics">
    <Askable ctx={ctx} meta={{ metric: 'revenue' }} text="Revenue card" scope="analytics">
      <Pressable>
        <Text>Revenue</Text>
      </Pressable>
    </Askable>
  </Askable>
</Askable>

ctx.toPromptContext();
// → "User is focused on: — view: dashboard > tab: finance > metric: revenue — value \"Revenue card\""

useAskable(options?)

Returns a React Native-friendly wrapper around an AskableContext.

ts
import { useAskable } from '@askable-ui/react-native';

const { focus, promptContext, ctx } = useAskable();

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
namestringOptional context name passed through to createAskableContext()
viewportbooleanOptional viewport-aware core context mode
eventsAskableEvent[]Forwarded to the underlying core context

Returns:

ValueTypeDescription
focusAskableFocus | nullCurrent focus created by press-driven updates
promptContextstringNatural-language context string for your LLM prompt
ctxAskableContextFull context instance for push(), clear(), toHistoryContext(), etc.

Inspector

React Native does not use the floating browser overlay inspector. createAskableInspector() is DOM-based, so it is only relevant on web targets.

For React Native development, the equivalent debugging pattern is to inspect focus, promptContext, and ctx.toHistoryContext() directly in your own debug UI or logs:

tsx
function AskableDebugPanel() {
  const { focus, promptContext } = useAskable();

  return __DEV__ ? (
    <View>
      <Text>{focus ? JSON.stringify(focus.meta) : 'No focus yet'}</Text>
      <Text>{promptContext}</Text>
    </View>
  ) : null;
}

If you share a custom ctx across a screen, read from that same ctx in your debug panel so the mobile debugging surface reflects the same Askable context as your press/visibility helpers.

Shared vs private/custom contexts

React Native differs from the web adapters: useAskable() creates a private context per hook call unless you provide ctx.

  • Private default — each useAskable() call gets its own AskableContext.
  • Custom shared context — create one context and pass it to useAskable({ ctx }), <Askable ctx={ctx} />, useAskableScreen({ ctx }), useAskableScrollView({ ctx }), and useAskableVisibility({ ctx }) when one screen or app surface should share focus/history.
  • Multiple isolated surfaces — create separate contexts when different tabs/screens/chats should not affect each other.
tsx
import { createAskableContext } from '@askable-ui/core';
import { Askable, useAskable, useAskableScreen } from '@askable-ui/react-native';

// Shared screen-level context
const screenCtx = createAskableContext();

function RevenueScreen() {
  const askable = useAskable({ ctx: screenCtx });

  useAskableScreen({
    ctx: screenCtx,
    meta: { screen: 'RevenueScreen' },
    text: 'Revenue screen',
  });

  return (
    <Askable ctx={screenCtx} meta={{ widget: 'revenue' }} text="Revenue card">
      <Pressable>{/* ... */}</Pressable>
    </Askable>
  );
}

// Separate private context for another surface
function SupportChatScreen() {
  const support = useAskable();
  return null;
}

Use the private default for isolated screens. Provide ctx explicitly when multiple React Native helpers/components should cooperate on one Askable context.

useAskableScreen(options)

Pushes screen-level context into the shared AskableContext while a screen is active.

tsx
import { useIsFocused } from '@react-navigation/native';
import { useAskable, useAskableScreen } from '@askable-ui/react-native';

function RevenueScreen() {
  const isFocused = useIsFocused();
  const { ctx } = useAskable();

  useAskableScreen({
    ctx,
    active: isFocused,
    meta: { screen: 'RevenueScreen' },
    text: 'Revenue screen',
  });

  return null;
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
metaRecord<string, unknown> | stringScreen metadata pushed when the screen is active
textstringOptional label stored alongside the screen metadata
activebooleanWhether the screen is currently focused. Default: true
clearOnBlurbooleanClear the context when the screen becomes inactive. Default: true
name / viewport / eventsCore optionsForwarded when the hook creates its own context

useAskableScrollView(options)

Mirrors raw ScrollView measurement into the shared AskableContext by tracking child layouts and selecting the top visible measured item.

tsx
import { Pressable, ScrollView, Text } from 'react-native';
import { Askable, useAskable, useAskableScrollView } from '@askable-ui/react-native';

const cards = [
  { id: 'rev', title: 'Revenue', meta: { widget: 'revenue' } },
  { id: 'pipe', title: 'Pipeline', meta: { widget: 'pipeline' } },
];

function DashboardFeed() {
  const { ctx } = useAskable();
  const { onScroll, createOnItemLayout } = useAskableScrollView({
    ctx,
    getMeta: (card) => ({ ...card.meta, visible: true }),
    getText: (card) => `${card.title} is leading the dashboard scroll view`,
  });

  return (
    <ScrollView onScroll={onScroll} scrollEventThrottle={16}>
      {cards.map((card) => (
        <Askable key={card.id} ctx={ctx} meta={card.meta} text={card.title}>
          <Pressable onLayout={createOnItemLayout(card.id, card)}>
            <Text>{card.title}</Text>
          </Pressable>
        </Askable>
      ))}
    </ScrollView>
  );
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
activebooleanWhether scroll updates should currently affect focus. Default: true
clearOnBlurbooleanClear existing scroll focus when tracking becomes inactive. Default: true
getMeta(item, measured) => Record<string, unknown> | stringMaps the visible measured item into askable metadata
getText(item, measured) => stringOptional human-readable label stored alongside the metadata
selectVisible(items) => item | nullOverride which measured item should win focus. Default: the top-most visible item
name / viewport / eventsCore optionsForwarded when the hook creates its own context

Returns:

ValueTypeDescription
ctxAskableContextContext instance used for scroll-driven focus updates
onScroll(event) => voidAttach to ScrollView.onScroll
measureItem(key, item, layout) => voidManually register/update a child layout
unmeasureItem(key) => voidRemove a child layout when it leaves the tree
clearVisibleItem() => voidClear the current scroll-driven focus
createOnItemLayout(key, item) => onLayoutHandlerConvenience helper for onLayout wiring

useAskableVisibility(options)

Mirrors FlatList / SectionList viewability callbacks into the shared AskableContext.

tsx
import { FlatList, Text, View } from 'react-native';
import { useAskable, useAskableVisibility } from '@askable-ui/react-native';

const rows = [
  { id: 'deal-1', title: 'Enterprise renewal' },
  { id: 'deal-2', title: 'Expansion pipeline' },
];

function DealsList() {
  const { ctx } = useAskable();
  const { onViewableItemsChanged } = useAskableVisibility({
    ctx,
    getMeta: (item) => ({ dealId: item.id }),
    getText: (item) => item.title,
  });

  return (
    <FlatList
      data={rows}
      keyExtractor={(item) => item.id}
      onViewableItemsChanged={onViewableItemsChanged}
      renderItem={({ item }) => (
        <View>
          <Text>{item.title}</Text>
        </View>
      )}
    />
  );
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
activebooleanWhether visibility updates should currently affect focus. Default: true
clearOnBlurbooleanClear existing visibility focus when tracking becomes inactive. Default: true
getMeta(item, token) => Record<string, unknown> | stringMaps the visible row into askable metadata
getText(item, token) => stringOptional human-readable label stored alongside the metadata
selectViewable(tokens) => token | nullOverride which visible token should win focus. Default: the first visible item
name / viewport / eventsCore optionsForwarded when the hook creates its own context

Notes

  • This adapter currently covers press-driven interactions, lightweight screen-awareness, raw ScrollView measurement, and list viewability callbacks.
  • useAskableScreen() is designed to pair with React Navigation's useIsFocused() or a similar focus signal.
  • useAskableScrollView() is a good fit for custom dashboard layouts built with ScrollView.
  • useAskableVisibility() is ideal for FlatList / SectionList.
  • Existing child onPress / onLongPress handlers are preserved and still run.

Released under the MIT License.