# Components API

> Component status and framework-specific API differences.

SymbioteNative's component goal is structural parity: reusable state, render, and
native plumbing live in shared layers; adapters provide lifecycle and framework
syntax.

## Installation

```sh
pnpm add @symbiote-native/components
```

Nearly every consumer gets this transitively: `@symbiote-native/react`,
`@symbiote-native/vue`, `@symbiote-native/angular`, `@symbiote-native/svelte`,
and `@symbiote-native/solid` each depend on it and re-export the parts an app
touches. Install it directly only when building a new adapter or a
native-view wrapper. `@symbiote-native/engine` and `react-native` (>=0.86)
are peer dependencies and stay top-level dependencies of the app.

## Status matrix

`view`, `text`, `pressable`,
`image`, `image-background`, `scroll-view`, `text-input`, `switch`,
`activity-indicator`, `safe-area-view`, `modal`, `refresh-control`,
`input-accessory-view`, `button`, `touchable-opacity`,
`touchable-native-feedback`, `touchable-without-feedback`,
`touchable-highlight`, and `sticky-header` are lowercase tags an app writes
directly — React, Vue, Svelte, and Solid resolve them with no import; Angular
resolves them once its `imports` array carries `SYMBIOTE_ELEMENTS`. There is
no per-adapter wrapper component left for these, so the framework differences
that used to live in each adapter's own render bridge (`descriptorToReact`,
`descriptorToVue`, a hand-authored Svelte host tag, Solid's
`descriptorToSolid`) mostly don't apply to them anymore — a `pressable` on
React and a `pressable` on Solid commit the identical Fabric tree, built by
the same shared host behavior in `@symbiote-native/components`/`@symbiote-native/engine`.

What still differs by framework:

- **Event syntax** — `onPress` (React/Svelte/Solid), `@press` (Vue),
  `(press)` (Angular). See [Event naming
  examples](#event-naming-examples) below.
- **`pressable`'s pressed state** no longer arrives as a render-prop child, a
  Vue scoped slot, or a Svelte snippet — those relied on a wrapper component
  body to call a child function, and a plain tag has no such body. Wire
  `onPressIn`/`onPressOut` into local state instead (each framework's own API
  page shows the idiom); Angular already used real `(pressIn)`/`(pressOut)`
  outputs for this and is unaffected.
- **Lists** (`FlatList`, `SectionList`, `VirtualizedList`,
  `VirtualizedSectionList`) and `KeyboardAvoidingView` are the one family
  still a genuine per-framework component: `renderItem`/the cell content
  returns a framework element (`ReactNode`, a Vue slot, a Svelte snippet, a
  Solid render function), which is exactly the shape a tag cannot express —
  see each framework's own API page for the render-item signature.
- **`Animated.*` wrapping** stays per-adapter — see the [Animations
  guide](/docs/learn/animations/) for how each framework hands an animated
  value into a tag's `style` prop and what `createAnimatedComponent` still
  does where a genuine component (the lists) is being animated.
- `PanResponder` — an engine-level gesture-recognition module, not a
  component; shared across every adapter unchanged.

## Shared vs framework-specific

Shared when the prop is plain native data:

- booleans, strings, numbers;
- native style objects;
- accessibility and ARIA aliases;
- platform constants and native payload shapes.

Framework-specific when the prop contains framework values:

- children or slots;
- render callbacks returning framework elements;
- refs and imperative handles;
- framework-level event conventions.

## Event naming examples

| Tag          | React           | Vue                               | Angular         | Svelte          | Solid           | Payload          |
| ------------ | --------------- | --------------------------------- | --------------- | --------------- | --------------- | ---------------- |
| `pressable`  | `onPress`       | `@press`                          | `(press)`       | `onPress`       | `onPress`       | `ISymbioteEvent` |
| `pressable`  | `onLongPress`   | `@long-press`                     | `(longPress)`   | `onLongPress`   | `onLongPress`   | `ISymbioteEvent` |
| `switch`     | `onValueChange` | `@value-change`<br />or `v-model` | `(valueChange)` | `onValueChange` | `onValueChange` | `boolean`        |
| `text-input` | `onValueChange` | `@value-change`<br />or `v-model` | `(valueChange)` | `onValueChange` | `onValueChange` | `string`         |
| `view`       | `onLayout`      | `@layout`                         | `(layout)`      | `onLayout`      | `onLayout`      | `ISymbioteEvent` |

`text-input`'s React/Vue callback fires as `(text, event)` — one value merged
from what used to be two separate callbacks. Angular can't do that in one
`EventEmitter`, so it keeps a second, separate `(change)` output for the raw
event alongside text-only `(valueChange)`. Svelte's `onValueChange` fires the
identical `(text, event)` shape React's does — the adapter is a flat
object-bag adapter (every prop, handlers included, rides one object handed to
the shim element's `p` setter), so there is no per-event translation layer to
begin with: the callback prop name is RN's own name, unchanged. Solid's
`onValueChange` fires the same `(text, event)` shape too, as an ordinary
callback prop: Solid JSX props reach the component as plain getters, with no
compiler-level event translation.

Angular's names (`(press)`, `(longPress)`, `(valueChange)`, `(change)`,
`(layout)`) are all real `@Output()` `EventEmitter`s, bound like
`(press)="handler($event)"`. The one permanent exception is the scroll-family
events (`onScroll`, `onScrollBeginDrag`, `onScrollEndDrag`,
`onMomentumScrollBegin`, `onMomentumScrollEnd`) on `scroll-view` and the list
components, which stay plain callbacks bound as an `@Input()` —
`[onScroll]="handler"` — because they can carry an `Animated.event(...)`
marker for native-driven scroll, and `@Output()` only binds a template
listener expression, never an arbitrary value. Svelte has no `@Output()`
equivalent at all — every event on every component, scroll-family included,
is a plain callback prop, so this Angular-only exception has no Svelte
counterpart to carve out. Solid has no `@Output()` equivalent either, for the
same reason.

Svelte's own two-way-binding primitive, the nearest equivalent to Vue's
`v-model`, is `$bindable()` (`let { value = $bindable() } = $props()`, wired
with `bind:value={x}` at the call site). `text-input`/`switch`
(and `Slider`, from the separate `@symbiote-native/slider` package) declare
`value` this way, so both styles work: `<text-input bind:value={x} />` and the
explicit `<text-input value={x} onValueChange={setX} />` that Svelte shares
with React. Supplying `onValueChange` on the same instance as `bind:value`
takes over the echo — the component can't detect binding from the inside, so
pass one or the other, not both, on one control. See [How to: two-way bind a
value](/docs/howtos/two-way-binding/) for the full picture. Solid has no
`bind:value`/`v-model` sugar of its own on these components: `value` and
`onValueChange` are always explicit, the same pair React uses without the
sugar.

## Framework event idioms side by side

<Tabs syncKey="framework">
  <TabItem label="React">
    ```tsx
    // React — callback props, no import needed for the tags
    <pressable onPress={event => console.log(event)} />
    <switch value={enabled} onValueChange={setEnabled} />
    ```
  </TabItem>
  <TabItem label="Vue">
    ```vue
    <!-- Vue — attrs listeners, or v-model on controlled tags -->
    <pressable @press="event => console.log(event)" />
    <switch v-model="enabled" />
    ```
  </TabItem>
  <TabItem label="Angular">
    ```ts
    // Angular — @Output() everywhere, except the scroll-family callback @Input()s
    @Component({
      imports: [SYMBIOTE_ELEMENTS],
      template: `
        <pressable (press)="onPress($event)" />
        <switch [value]="enabled" (valueChange)="setEnabled($event)" />
      `,
    })
    ```
  </TabItem>
  <TabItem label="Svelte">
    ```svelte
    <!-- Svelte — callback props, same names as React; no emits, no @Output() -->
    <script lang="ts">
      let enabled = $state(false);
    </script>

    <pressable onPress={event => console.log(event)} />
    <switch value={enabled} onValueChange={next => (enabled = next)} />
    ```

  </TabItem>
  <TabItem label="Solid">
    ```tsx
    // Solid — callback props, same names as React; no emits, no @Output(), no bind: sugar
    import { createSignal } from 'solid-js';

    const [enabled, setEnabled] = createSignal(false);

    <pressable onPress={event => console.log(event)} />
    <switch value={enabled()} onValueChange={setEnabled} />
    ```

  </TabItem>
</Tabs>

## API

App code reaches these through an adapter. The exports below are the seam an
adapter (or a native-view wrapper package) drives directly.

### Descriptor model

A render function returns a `Descriptor` tree — a tiny framework-agnostic node
description. For a primitive tag (`view`, `switch`, `modal`, ...), the engine's
own `IHostBehavior.buildStructure` consumes this directly at attach time — no
adapter is involved. The `descriptorToReact`/`descriptorToVue`/`descriptorToSolid`
bridges (and Svelte's equivalent host-tag wiring) still exist for the one
family that remains a genuine per-framework component — the lists
(`FlatList`, `SectionList`, `VirtualizedList`, `VirtualizedSectionList`) and
`KeyboardAvoidingView` — since a list's cell content is a real framework
element, not a `Descriptor`.

| Export             | Signature                                        | Description                                                                                                                                                |
| ------------------ | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `el`               | `el(type, props?, children?, key?): IDescriptor` | Build a host-element descriptor of any type; `type` is an open string, so a component can paint a raw Fabric view name as well as a primitive tag name |
| `txt`              | `txt(props?, children?): IDescriptor`            | Shorthand for a `text` descriptor                                                                                                                 |
| `IDescriptor`      | `{ type, props, children, key? }`                | The node every render function returns                                                                                                                     |
| `IDescriptorType`  | `string`                                         | The host component to paint — kept open, since components register their own host element names with the engine                                            |
| `IDescriptorProps` | `Record<string, unknown>`                        | Open prop bag (style, events, accessibility, native props), forwarded onto the framework element verbatim                                                  |
| `IDescriptorChild` | `IDescriptor \| string`                          | A nested descriptor or a raw text child                                                                                                                    |

### Native component names

| Export                  | Signature                                                                 | Description                                                                                                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `descriptorFor`         | `descriptorFor(type: string): IComponentDescriptor`                       | Resolve an intrinsic to its Fabric component name for the current platform. An unknown intrinsic throws (a typo in our own code); any other string passes through as a raw Fabric view name from a library's codegen component |
| `COMPONENT_DESCRIPTORS` | `Readonly<Record<string, IComponentDescriptor>>`                          | The platform-selected intrinsic → Fabric name map. The tables are Metro-split (`.ios`/`.android` filename selects, no `Platform.OS` read)                                                                                              |
| `buildDescriptors`      | `buildDescriptors(names): Readonly<Record<string, IComponentDescriptor>>` | Assemble the descriptor map a platform name table exports, pairing each name with its platform-invariant `isText` flag                                                                                                                 |
| `makeDescriptorFor`     | `makeDescriptorFor(descriptors): (type: string) => IComponentDescriptor`  | Bind the resolver above to one descriptor map; each platform file uses it to produce its own `descriptorFor`                                                                                                                           |
| `ISymbioteIntrinsic`    | union of `'view'`, `'text'`, `'image'`, …                                 | Every intrinsic tag an app may write. A name table must cover exactly these keys, so a missing primitive is a compile error rather than a runtime gap                                                                                   |
| `IComponentDescriptor`  | `{ component: string; isText: boolean }`                                  | The resolved Fabric name plus whether it lays text (drives the `RCTText`/`RCTVirtualText` nesting choice)                                                                                                                              |

### Render functions

Pure `viewProps → Descriptor`. Visual state enters only through arguments; no
framework, no lifecycle, no events.

| Function                   | Signature                                                                                  | Description                                                                                          |
| -------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `renderSwitch`             | `(view: ISwitchViewProps, platform: ISwitchPlatform) => IDescriptor`                       | Paint the switch, mapping the track/thumb color props onto the platform's native names               |
| `renderImage`              | `(view: IImageViewProps) => IDescriptor`                                                   | Resolve the source, fold the `width`/`height` aliases into style, paint `image`             |
| `renderInputAccessoryView` | `(view: IInputAccessoryViewViewProps) => IDescriptor`                                      | Assemble the `nativeID`/`backgroundColor` host element                                               |
| `renderModal`              | `(view: IModalViewProps) => IDescriptor`                                                   | Paint the modal host, applying the transparent/backdrop overrides on top of the generic `style` prop |
| `renderTextInput`          | `(view: ITextInputViewProps) => IDescriptor`                                               | Pick the single-line or multiline intrinsic and map the resolved native props                        |

These are called by the primitive's own `IHostBehavior.buildStructure` in the
engine now, not by an adapter — that's what lets a bare `<modal>`/`<switch>`/…
tag commit the same tree a wrapper used to. `ActivityIndicator`'s and
`ImageBackground`'s painting logic moved into their behaviors directly
(`behaviors/activity-indicator/`, `behaviors/image-background.ts`) with no
separate `render*` function left. The list family still builds its cell
content from the framework's own elements, so the shared half there is the
state and math below, not a descriptor.

### State machines

Pure reducers and folds. The adapter supplies the lifecycle cell (React
`useReducer`, Vue `ref`/`watch`, Angular signals, Svelte `$state`/`$effect`
runes, Solid `createSignal`/`createEffect`) and executes the effects.

| Export                            | Signature                                                                                                   | Description                                                                                                                                                                                     |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createInitialSwitchState`        | `() => ISwitchState`                                                                                        | Initial `Switch` state — no native report seen yet                                                                                                                                              |
| `switchReducer`                   | `(state: ISwitchState, action: ISwitchAction) => ISwitchState`                                              | Fold a native `change` report into the last-reported value; always returns a fresh object so the snap-back effect re-fires on every report                                                      |
| `shouldSnapBack`                  | `(state: ISwitchState, fabricValue: boolean) => boolean`                                                    | Whether native reported a value the JS-held value rejects, so the switch must be commanded back                                                                                                 |
| `valueFromChange`                 | `(event: ISymbioteEvent) => boolean \| undefined`                                                           | Read the boolean out of a native `Switch` change payload                                                                                                                                        |
| `createInitialModalState`         | `(isVisible: boolean) => IModalState`                                                                       | Initial `Modal` state, seeded from the first `visible` prop                                                                                                                                     |
| `modalReducer`                    | `(state: IModalState, action: IModalAction) => IModalState`                                                 | Gate the iOS keep-alive frame across show/hide                                                                                                                                                  |
| `shouldRenderModal`               | `(isVisible: boolean, state: IModalState) => boolean`                                                       | Whether the modal host should be in the tree this render                                                                                                                                        |
| `createPressRuntime`              | `() => IPressRuntime`                                                                                       | Mutable per-instance press bookkeeping (timers, origin, suppression flags) the handlers write through                                                                                           |
| `createPressHandlers`             | `(config: IPressMachineConfig, runtime: IPressRuntime, host: IPressHost) => IPressHandlers`                 | The whole press lifecycle — delay, long-press timer, drift test against the responder region, termination                                                                                       |
| `buildPressableListeners`         | `(handlers: IPressHandlers, options: { disabled?, cancelable? }) => Record<string, unknown>`                | Turn those handlers into the responder listener bag a host element takes; returns `{}` when disabled                                                                                            |
| `createTouchableFeedbackRuntime`  | `() => ITouchableFeedbackRuntime`                                                                           | Per-instance timing state for the `Touchable*` feedback animation                                                                                                                               |
| `createTouchableFeedbackHandlers` | `(config, runtime, callbacks) => ITouchableFeedbackHandlers`                                                | Press handlers for the `Touchable*` family; the adapter supplies the `Animated` animation through `callbacks`                                                                                   |
| `computePressOutWait`             | `(heldFor: number, minPressDuration: number, delayPressOut: number) => number`                              | The deactivation floor — how long to keep the pressed visual after release                                                                                                                      |
| `resolveTextInputProps`           | `(input: ITextInputFoldInput) => IFoldedTextInputProps`                                                     | Fold `inputMode`/`autoComplete`/`submitBehavior` and friends into the native prop set                                                                                                           |
| `foldText`                        | `(value?: string, defaultValue?: string) => string \| undefined`                                            | Resolve the controlled `value` against `defaultValue`                                                                                                                                           |
| `textFromChange`                  | `(event: ISymbioteEvent) => string \| undefined`                                                            | Read the text out of a native `TextInput` change payload                                                                                                                                        |
| `eventCountFromChange`            | `(event: ISymbioteEvent) => number \| undefined`                                                            | Read the native event count, the ordering token behind the controlled-write handshake                                                                                                           |
| `shouldCommandText`               | `(lastNativeText: string \| undefined, value: string \| undefined) => value is string`                      | Whether JS must command text back onto the native input because the two have diverged                                                                                                           |
| `createInitialListState`          | `<ItemT>() => IListState<ItemT>`                                                                            | Initial `VirtualizedList` state — no offsets measured, empty committed window                                                                                                                   |
| `reduceList`                      | `<ItemT>(state, action: IListAction<ItemT>, inputs: IListReducerInputs<ItemT>) => IListReduceResult<ItemT>` | The list orchestration reducer: window recompute → edge reached → viewability → initial scroll → `maintainVisibleContentPosition`, returned as state plus an effect list for the adapter to run |
| `listEffectSignature`             | `<ItemT>(state: IListState<ItemT>) => string`                                                               | Stable key over the state fields an effect depends on, so an adapter can skip re-running unchanged effects                                                                                      |
| `createInitialStickyState`        | `() => IStickyHeaderState`                                                                                  | Initial sticky-header state — unmeasured, untranslated                                                                                                                                          |
| `reduceSticky`                    | `(state, action: IStickyAction, inputs: IStickyReducerInputs) => IStickyReduceResult`                       | The per-header sticky machine: the zero-swallow gate, the debounce-delay pick, and the rebuild-on-input-change decision                                                                         |
| `stickyEffectSignature`           | `(state: IStickyHeaderState) => string`                                                                     | The same skip-unchanged key for the sticky interpolation effect                                                                                                                                 |

### ScrollView, lists, and layout helpers

Platform-invariant math and plumbing with no state machine of their own.

| Export                          | Signature                                                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolveDecelerationRate`       | `(rate: 'normal' \| 'fast' \| number) => number`                             | Map RN's named deceleration rates to the numeric native value                                                                                                                                                                                                                                                                                                                                                                    |
| `selectScrollIntrinsics`        | `(isHorizontal: boolean, contentContainerStyle) => IScrollIntrinsics`        | Pick the scroll host/content intrinsics — horizontal scroll is a separate native `ViewManager` on Android                                                                                                                                                                                                                                                                                                                        |
| `resolveScrollForwarding`       | `(inputs: IScrollForwardingInputs) => IScrollForwarding`                     | The scroll-forwarding decisions: which `onScroll` path to build (`IScrollForwardMode` — `'plain'`, `'sticky-native'`, `'sticky-js'`), the resolved `scrollEventThrottle`, whether `onLayout` must capture the viewport height, and whether content cells stay un-flattened for `maintainVisibleContentPosition`/snap on Android. It returns decisions, not built handlers — those must stay framework-owned for identity reasons |
| `buildScrollViewHandle`         | `(getNode: () => ISymbioteNode \| null) => IScrollViewHandle`                | The imperative handle (`scrollTo`, `scrollToEnd`, `flashScrollIndicators`, …) over a lazily-read host node                                                                                                                                                                                                                                                                                                                       |
| `splitLayoutProps`              | `(style) => { outer, inner }`                                                | RN's key partition: layout keys (`margin*`, `flex`, …) go to the outer box, visual keys (`background*`, `padding*`, `border*`, …) stay on the inner view, for cases like the Android `RefreshControl` wrap                                                                                                                                                                                                                       |
| `forwardScrollEvent`            | `(handler, args: readonly unknown[]) => void`                                | Forward a native scroll event to a user handler, dropping non-event arguments                                                                                                                                                                                                                                                                                                                                                    |
| `isSymbioteEvent`               | `(value: unknown) => boolean`                                                | Runtime guard for a normalized engine event                                                                                                                                                                                                                                                                                                                                                                                      |
| `attachStickyScroll`            | `(node: ISymbioteNode, value: AnimatedValue) => () => void`                  | Attach a native-driven `onScroll` → `Animated.Value` binding; returns the detach function                                                                                                                                                                                                                                                                                                                                        |
| `computeStickyInterpolation`    | `(params: IStickyInterpolationParams) => { inputRange, outputRange }`        | The sticky header's interpolation ranges, including the inverted-list case                                                                                                                                                                                                                                                                                                                                                       |
| `nextStickyHeaderY`             | `(stickyHeaderIndices, indexOfIndex, headerLayoutYs) => number \| undefined` | The following sticky header's Y, which bounds how far the current one travels                                                                                                                                                                                                                                                                                                                                                    |
| `buildOffsets`                  | `(count, measured, fixedLayout, averageLength) => …`                         | Cumulative cell offsets from measured cells, a fixed-layout function, or the running average                                                                                                                                                                                                                                                                                                                                     |
| `computeWindow`                 | `(count, offsets, lengths, scrollOffset, …) => …`                            | The render window for the current scroll position                                                                                                                                                                                                                                                                                                                                                                                |
| `buildListPlan`                 | `(params: IListPlanParams) => IListPlan`                                     | The full cell plan for a pass — which cells render, plus the leading/trailing spacer extents                                                                                                                                                                                                                                                                                                                                     |
| `computeViewableSet`            | `<ItemT>(params: IViewableSetParams<ItemT>) => { tokens, map }`              | The currently viewable items, as tokens plus a key-indexed map                                                                                                                                                                                                                                                                                                                                                                   |
| `diffViewable`                  | `<ItemT>(previous, current, currentTokens) => { changed, hasChanged }`       | Diff two viewable sets into the payload `onViewableItemsChanged` expects                                                                                                                                                                                                                                                                                                                                                         |
| `resolveItemKey`                | `<ItemT>(item, index, keyExtractor?) => string`                              | The cell key, falling back to the index when no `keyExtractor` is given                                                                                                                                                                                                                                                                                                                                                          |
| `chunkIntoRows`                 | `<ItemT>(data: readonly ItemT[], columns: number) => IRow<ItemT>[]`          | Group flat data into `numColumns` rows for `FlatList`                                                                                                                                                                                                                                                                                                                                                                            |
| `rowKeyExtractor`               | `<ItemT>(row: IRow<ItemT>) => string`                                        | Stable key for such a row                                                                                                                                                                                                                                                                                                                                                                                                        |
| `flattenSections`               | `<ItemT>(sections, withSeparators) => { entries, headerIndices }`            | Flatten `SectionList` sections into one entry list plus the sticky header indices                                                                                                                                                                                                                                                                                                                                                |
| `unwrapEntryItem`               | `<ItemT>(entry?: ISectionEntry<ItemT>) => ItemT \| undefined`                | Read the item out of a flattened entry, or `undefined` for a header/separator entry                                                                                                                                                                                                                                                                                                                                              |
| `sectionEntryKey`               | `<ItemT>(entry, index, keyExtractor?) => string`                             | Key for a flattened section entry                                                                                                                                                                                                                                                                                                                                                                                                |
| `scrollLocationToFlatIndex`     | `(headerIndices, sectionIndex, itemIndex) => number`                         | Translate a `scrollToLocation` section/item pair into the flat index the windowing math uses                                                                                                                                                                                                                                                                                                                                     |
| `resolveKeyboardAvoidingLayout` | `(params: IResolveKeyboardAvoidingLayoutParams) => IKeyboardAvoidingLayout`  | The resolved style/height for `KeyboardAvoidingView`'s current behavior                                                                                                                                                                                                                                                                                                                                                          |
| `computeInset`                  | `(frame, keyboard, verticalOffset) => number`                                | The overlap between the measured frame and the keyboard frame                                                                                                                                                                                                                                                                                                                                                                    |
| `readKeyboardFrame`             | `(payload: unknown) => IKeyboardFrame \| undefined`                          | Guard-read a native keyboard event payload                                                                                                                                                                                                                                                                                                                                                                                       |
| `readLayoutFrame`               | `(layout: unknown) => IMeasuredFrame \| undefined`                           | Guard-read an `onLayout` payload                                                                                                                                                                                                                                                                                                                                                                                                 |
| `buttonTextStyle`               | `ITextStyle`                                                                 | The base `Button` label style                                                                                                                                                                                                                                                                                                                                                                                                    |
| `resolveButtonTextStyle`        | `(color?: string, disabled?: boolean) => ITextStyle`                         | Fold `color`/`disabled` into that base style                                                                                                                                                                                                                                                                                                                                                                                     |
| `BUTTON_ACCESSIBILITY_ROLE`     | `'button'`                                                                   | The role every adapter's `Button` sets                                                                                                                                                                                                                                                                                                                                                                                           |

Constants with the same shared-math role are exported alongside them —
`DEFAULT_DELAY_LONG_PRESS_MS`, `DEFAULT_PRESS_RECT_OFFSETS`,
`DEFAULT_ACTIVE_OPACITY`, `DEFAULT_VERTICAL_OFFSET`, `DEFAULT_WINDOW_SIZE`,
`DEFAULT_INITIAL_NUM_TO_RENDER`, `SCROLL_VIEW_BASE_VERTICAL`,
`STICKY_HEADER_Z_INDEX`, `INITIAL_EVENT_COUNT`, among others.

### Accessibility

| Export                                                                        | Signature                                                     | Description                                                                                                                                                               |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolveAccessibilityProps`                                                   | `<T extends IAccessibilityProps & IAriaProps>(props: T) => T` | Fold the web aliases (`role`, `aria-*`) into the canonical `accessibility*` props. Returns `props` untouched when no alias is present, so every adapter folds identically |
| `IAccessibilityProps`                                                         | type                                                          | The canonical `accessibility*` prop set                                                                                                                                   |
| `IAriaProps`                                                                  | type                                                          | The `role`/`aria-*` aliases that fold into it                                                                                                                             |
| `IAccessibilityRole`, `IRole`                                                 | type                                                          | The native role union and its web-alias twin                                                                                                                              |
| `IAccessibilityStateValue`, `IAccessibilityValue`, `IAccessibilityActionInfo` | type                                                          | State, value, and custom-action payload shapes                                                                                                                            |

### Shared prop types

`IResponderProps`, `IActivityIndicatorProps`, `ISwitchProps`, and `IButtonProps`
are fully framework-agnostic at their core, so every adapter's own prop type
layers only its own class-styling field on top of the shared base rather than
redeclaring the whole shape — React adds `className?: string`, Vue, Svelte
and Solid all add `class?:` (Svelte's is `ISvelteClassValue`, a wider type
that additionally accepts a clsx-style object/array, resolved by the adapter
itself before it reaches the engine; Solid's is the plain `IClassNameValue`
the engine's style registry resolves, matching Vue's). `IImageProps`, `ITextInputProps`,
`ITextInputHandle`, `IScrollViewHandle`, `IVirtualizedListHandle`, and
`IVirtualizedSectionListHandle` ship here too; a prop type carrying framework
children, refs, or render callbacks stays per-adapter by design.

The `*ViewProps` types (`IActivityIndicatorViewProps`, `ISwitchViewProps`,
`IImageViewProps`, `IModalViewProps`, `ITextInputViewProps`,
`IImageBackgroundViewProps`, `IInputAccessoryViewViewProps`) are the inputs of
the matching `render*` function — already resolved, adapter-facing, not app-facing.

### Re-exported from the engine

`imageStatics` and `setImageSourceResolver` (plus `IImageStatics`, `IImageSize`,
`IImageCacheStatus`) originate in `@symbiote-native/engine` — they touch the native
bridge, so they stay out of the pure view layer — and are re-exported here so the
public `Image.*` surface an adapter assembles is unbroken.
