FRAMEWORKExperimental — not for production

Native UI and styling

Controlled inputs, native selectors, macOS composition, settings windows, and Uniwind.

Common controls

For Markdown and HTML readers, inputs, and editable previews, see Rich text.

Import Button, TextInput, Select, and SegmentedControl from @legendapp/spark/ui:

import { useState } from 'react';
import { View } from 'react-native';
import { Button, TextInput, Select } from '@legendapp/spark/ui';

export function Preferences() {
  const [name, setName] = useState('');
  const [theme, setTheme] = useState('system');
  return (
    <View>
      <TextInput value={name} onChangeText={setName} accessibilityLabel="Name" />
      <Select
        options={[
          { label: 'System', value: 'system' },
          { label: 'Light', value: 'light' },
          { label: 'Dark', value: 'dark' },
        ]}
        value={theme}
        onValueChange={setTheme}
        accessibilityLabel="Appearance"
      />
      <Button onPress={() => console.log({ name, theme })}>Save</Button>
    </View>
  );
}

Controls share disabled, accessibilityLabel, testID, layout style, onError, and a ControlRef supporting measureInWindow. Shared refs do not expose backend objects or a universal focus/text mutation API.

TextInput is single-line and supports either controlled value or uncontrolled defaultValue. Those modes are mutually exclusive; remount to switch modes/reset an uncontrolled field. Defaults initialize once. Controlled desktop edits acknowledge native event counts; mobile controlled editing uses React Native TextInput.

Select and SegmentedControl share unique string-valued options and controlled value/onValueChange. Labels may repeat; a missing selected value does not implicitly select the first option. A parent that declines a choice retains its current selection.

ControlmacOSWindowsiOSAndroidWeb
ButtonAppKitWinUIExpo SwiftUIExpo ComposeHTML button
TextInputAppKitWinUIRN TextInputRN TextInputHTML input
SelectPopupComboBoxMenu pickerInline choicesHTML select
SegmentedControlSegmented controlUnsupportedSegmented pickerInline choicesButton group

getControlAvailability is synchronous. Missing/unsupported native controls report through onError (default console logging) and render a disabled fallback with current label/text. Invalid props throw during render. A fallback is not a functioning native control.

Mobile Button/Select use optional @expo/ui@0.2.0-beta.9 with Expo 54; Compose requires a development build. Windows native controls still need validation.

Specialized macOS views

ImportContract
/ui/searchTextInputSearch, controlled/default input, explicit appearance, focus/blur/measurement ref
/ui/sidebarControlled nullable selectedId, required onSelectionChange, item-array or SidebarItem composition, typed context-menu coordinates
/ui/split-viewSidebarSplitView with named sidebar/content panes, an optional list column, grouped title-bar options and provisional/ready layout events
/ui/swipeSwipeActions with leadingActions/trailingActions, required onAction, and getSwipeActionsAvailability; SwipeAction/SwipeActionsProps types
/ui/glassGlassView, regular/clear styles and RN color tint; native effect requires macOS 26
/ui/symbolSFSymbol, Apple-specific symbol names, layout/accessibility props and missing-symbol errors

These views have safe synchronous availability queries and onError. Unsupported hosts preserve ordinary layout/content and report the limitation. Split layout readiness comes from native events; initial metrics are hints. Zero pane dimensions remain zero. Older macOS retains GlassView children without the effect.

import { SidebarSplitView } from '@legendapp/spark/ui/split-view';
import { View, Text } from 'react-native';

<SidebarSplitView
  sidebar={<View><Text>Navigation</Text></View>}
  list={<View><Text>Documents</Text></View>}
  content={<View><Text>Editor</Text></View>}
  titleBar={{ content: { height: 52, material: 'glass' } }}
  onResize={event => console.log(event.phase, event.contentX)}
/>

SidebarSplitView.list adds a content-list column between the sidebar and detail content. Omit it for a two-pane layout.

SwipeActions wraps a row with trackpad actions. Each SwipeAction has a unique id, title, SF Symbol symbol, and color (#RRGGBB or #RRGGBBAA). Swipe right to reveal leadingActions or left for trailingActions; a full swipe runs the first action. onAction receives its ID. In recycling lists, key the view by the logical row ID. Optional dismisses slides the row away; remove or remount it to recover after failure or undo.

import { SwipeActions } from '@legendapp/spark/ui/swipe';
import { Text } from 'react-native';

<SwipeActions
  trailingActions={[{ id: 'archive', title: 'Archive', symbol: 'archivebox', color: '#007AFF' }]}
  onAction={id => console.log(id)}
>
  <Text>Project notes</Text>
</SwipeActions>

Settings windows

/settings/window exports SettingsWindow, VirtualizedSettingsWindow, named props/pages, and createSettingsWindowOptions. Both use { id, title, render } pages and an explicit windowId. Use controlled selectedPageId/onSelectionChange or mount-time defaultPageId, with one selection owner.

This composition requires @legendapp/list and macOS split-view support. It shows a hidden window only after layout/initial-scroll readiness. When native split view is unavailable, the fallback has no readiness event and the settings window stays hidden. Handle onError; check availability before selecting this composition for a target.

Styling

/ui/uniwind exports the same four common controls with optional className integration. Classes shape layout around native controls; arbitrary OS chrome styling is not promised. Explicit style takes precedence. /ui/classnames is a library-specific clsx/tailwind-merge convenience.

Use ordinary React Native views/text for surrounding screens. Uniwind supports application theme choices system, light, and dark; theme persistence belongs to the app. Windows Appearance overrides update mounted WinUI controls through the native theme module.

See UI contracts and styling setup. Router integration and a broad cross-platform component catalog remain outside the supported scope.