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.