npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swiftbrowser/react

v0.6.7

Published

SwiftUI's components for React: a react-reconciler host that emits SwiftBrowser render ops into the @swiftbrowser/web renderer, so a pure React/TypeScript web app gets the same iOS UI as a SwiftUI app in the browser.

Readme

@swiftbrowser/react

SwiftUI's components for React. A pure React/TypeScript web app writes its screens with List, NavigationStack, Toggle, Picker and the rest, and gets the same iOS UI a SwiftUI app gets in SwiftBrowser: the components emit the render ops the Swift shim emits, and the web renderer draws them with its iOS layout engine, typography, animations, navigation bars, tab bars, sheets and alerts. No Swift, no WebAssembly: the package is React components and a renderer host.

It works in both directions with ordinary React, and the two nest: a SwiftUI tree can live inside a react-dom page, and ordinary React DOM can live inside a SwiftUI element (see Mixing with ordinary React).

The package is experimental.

npm install @swiftbrowser/react @swiftbrowser/web react react-dom react-reconciler

react-reconciler is a peer dependency, so the app picks the release that matches its React: 0.31 for React 19.0, 0.32 for 19.1, 0.33 for 19.2, 0.34 for 19.3 (npm install [email protected] on React 19.2, say); the package works with all four.

It is built for a bundler (Vite, or any that compiles TypeScript in dependencies): the renderer it drives comes from @swiftbrowser/web's TypeScript sources, as does the stylesheet the page imports. The stylesheet is the renderer's alone (the device's tokens, the frame, the elements): it leaves the page's own body, classes and box model as they are, and an island is as wide and as tall as the page's CSS makes it. With Vite, list @swiftbrowser/web in optimizeDeps.exclude so the renderer's bitmap worker resolves from its sources.

Getting started

A whole page

The simplest app is a page whose root is a SwiftUI tree. The page provides the renderer's stylesheet and a root element, and mount draws the tree in it. In the device frame (the markup of index.html here, with the iPhone bezel, status bar and the theme controls) the root is the #screen element; on a phone the renderer can also draw full screen.

import '@swiftbrowser/web/src/styles.css';
import { useState } from 'react';
import { mount, Label, List, NavigationLink, NavigationStack, Section, Text, Toggle } from '@swiftbrowser/react';

function Library() {
  const [downloads, setDownloads] = useState(true);
  return (
    <List navigationTitle="Library">
      <Section header="Reading">
        <NavigationLink destination={<Text>Detail</Text>}>
          <Text>The Swift Programming Language</Text>
        </NavigationLink>
      </Section>
      <Section header="Settings">
        <Toggle isOn={[downloads, setDownloads]}>
          <Label title="Automatic Downloads" systemImage="arrow.down.circle" />
        </Toggle>
      </Section>
    </List>
  );
}

mount(document.getElementById('screen')!, <NavigationStack><Library /></NavigationStack>);

mount returns the root and the renderer; setEnvironment({ colorScheme, dynamicTypeSize }) on it switches dark mode and the Dynamic Type size, as the demo page's controls do.

Inside an existing React app

In a react-dom app, SwiftUIView is a normal component that draws its children with the renderer in a box of the page's own layout, with the device's design tokens and none of its frame:

import '@swiftbrowser/web/src/styles.css';
import { SwiftUIView, List, Section, Toggle } from '@swiftbrowser/react';

function Settings({ dark, setDark }) {
  return (
    <SwiftUIView fit="content" colorScheme={dark ? 'dark' : 'light'} className="settings">
      <List>
        <Section header="Appearance">
          <Toggle title="Dark Mode" isOn={[dark, setDark]} />
        </Section>
      </List>
    </SwiftUIView>
  );
}

fit="content" makes the island as tall as its content, like a block in the page's flow; fit="fill" (the default) draws in the box the page sizes with CSS, which is what navigation stacks and tab views want, since they fill their screen. colorScheme and dynamicTypeSize are props. The island is a React root of its own: the page's React state reaches it through props as usual, and its contexts through contexts={[…]} (see Contexts across roots).

There is no status bar in a page, so safeArea stands in for one. A fill island keeps 10 points at the top, the way SwiftUI keeps a window's bars off its edge. Navigation bars sit below that inset and their background reaches up into it, and lists start below it. A content island has no insets. Pass a number for the top inset (safeArea={0} puts the bars flush with the edge) or an object such as safeArea={{ top: 24, bottom: 34 }}.

Try the demos

npm install
cd packages/react
npm run dev            # http://localhost:5174/ — Examples/Fidelity, in React
                       # http://localhost:5174/island.html — islands in a react-dom page
                       # http://localhost:5174/wide.html — a regular-width app: split view, table, inspector
npm run test:unit      # the React Fidelity tabs emit the same tree as the Swift app

?screen=<name> on the first page opens one of the Fidelity screens alone (list, detail, form, controls, typography, shapes, layout, tabs, sheet, alert, search, post, settings, reorder), like the Swift app's -screen argument; extras, more and web show the components beyond it.

Writing views

Components

The components are named as in SwiftUI and take the same arguments as props: <VStack alignment="leading" spacing={8}>, <Text font="headline">, <Image systemName="heart.fill" />, <Button title="Save" action={save} />, <Picker label="Plan" selection={[plan, setPlan]} options={plans} />, <Section header="Reading" footer="…">. Children are the content closure. ForEach takes data and a render function, with id, onDelete and onMove as in SwiftUI; Group applies its modifiers to each child.

Text is plain strings and numbers inside <Text>; nested <Text>s and inline <Image>s make a rich text (Text + Text), and italic, underline, strikethrough and tracking are its own props.

Modifiers as props

Every component takes SwiftUI's modifiers as props, and applies them in the order they are written, innermost first, so a chain reads the same:

<Image
  systemName="swift"
  font={system(17, 'semibold')}
  foregroundStyle="white"
  frame={{ width: 32, height: 32 }}
  background={{ fill: 'orange', in: <RoundedRectangle cornerRadius={8} /> }}
/>

is Image(systemName: "swift").font(.system(size: 17, weight: .semibold)) .foregroundStyle(.white).frame(width: 32, height: 32).background(.orange, in: RoundedRectangle(cornerRadius: 8)). Each modifier wraps the view in the same styled element the Swift shim emits, so the renderer lays it out identically. A modifier that must apply twice goes in modifiers={[…]}, an ordered list. A layered style is an array: foregroundStyle={['white', 'indigo']} is .foregroundStyle(.white, .indigo), whose second style a symbolRenderingMode="palette" symbol draws its disc in. The environment modifiers (buttonStyle, controlSize, pickerStyle, labelsHidden, symbolRenderingMode) are React contexts the views below read, and navigationTitle, toolbar, toolbarRole, searchable, sheet, alert, presentationDetents and preferredColorScheme attach to the enclosing screen, sheet or page, as the SwiftUI preferences do. Effects (blur, grayscale, saturation, brightness, contrast, hueRotation, colorInvert, colorMultiply, blendMode, mask, rotation3DEffect), layout hints (aspectRatio, scaledToFit, position, zIndex, alignmentGuide, ignoresSafeArea, allowsHitTesting, lineLimit ranges) and the lifecycle (onAppear, task with an AbortSignal for cancellation, onChangeOf) are modifiers too, as are draggable and dropDestination.

Values are plain data: colors by name ("red", "secondary", "secondarySystemBackground") or through Color (Color.opacity('black', 0.2), Color.gradient('teal'), Color.hex('#336699')); fonts by text style ("title2") or { textStyle: 'title2', weight: 'bold' } and system(size, weight, design); frames as { width, height, maxWidth: Infinity, alignment }; padding as true, a number, ['vertical', 6] or insets; gradients through LinearGradient, RadialGradient and AngularGradient.

Bindings

A binding is a useState pair, so isOn={[downloads, setDownloads]} reads like isOn: $downloads. Everything that takes one (Toggle, Picker, Slider, Stepper, TextField, TextEditor, DatePicker, TabView's selection, List's selection and editing, NavigationStack's path, the sheet and alert modifiers' isPresented) also takes a plain value with an onChange handler. The setter can be anything with that signature.

When the renderer sends an event (a tap, a switch, a selection), the handler's state updates render synchronously and the renderer gets the next pass inside the event, the way the Swift core answers one.

Navigation and presentation

NavigationStack holds the pushed values (or takes a path binding). NavigationLink pushes a value, which the enclosing screen's navigationDestination builds a screen for, or a destination view directly. A pushed screen's title takes the display mode of the screen below it unless it sets its own (large below a large title), as on iOS. TabView holds Tabs, each with a systemImage or an asset catalog image (a custom symbol) for its bar item. .sheet and .alert are modifiers whose content and actions render as root-level elements while presented; presentationDetents inside a sheet sets its detents. useDismiss() inside a pushed screen or a sheet pops or dismisses it.

Toolbars

ToolbarItem's placement takes SwiftUI's names. secondaryAction items collapse into the bar's More menu (an ellipsis at the trailing end), as on an iPhone; with toolbarRole="editor" on the screen, a regular width shows them in the bar before the trailing items, and only those that do not fit go into More. Items float in Liquid Glass capsules, as iOS 26's do: consecutive items of one placement share a capsule (in the navigation bar too, so two trailing ToolbarItems are one capsule, not two circles), and a ToolbarSpacer of that placement ends it. In the bar the capsules sit 12 apart; at the foot of the screen a flexible spacer (the default) pushes the groups apart and a fixed one keeps them centered, 8 apart. A swapped item ({playing ? <ToolbarItem>…stop…</ToolbarItem> : <ToolbarItem>…play…</ToolbarItem>}) changes on a capsule that stays.

<ToolbarItem placement="bottomBar">{undoButton}{redoButton}</ToolbarItem>
<ToolbarSpacer sizing="fixed" placement="bottomBar" />
<ToolbarItem placement="bottomBar">{toolButtons}</ToolbarItem>
<List
  navigationTitle="Notes"
  toolbarRole="editor"
  toolbar={
    <>
      <ToolbarItem placement="primaryAction">
        <Button action={compose}>
          <Image systemName="square.and.pencil" />
        </Button>
      </ToolbarItem>
      <ToolbarItem placement="secondaryAction">
        <Button title="Export" action={exportNotes} />
      </ToolbarItem>
      <ToolbarItem placement="secondaryAction">
        <Button title="Archive" action={archive} />
      </ToolbarItem>
    </>
  }
>
  {/* … */}
</List>

Lists

List and Form hold Sections and rows. selection={[…]} makes rows with a tag (or a NavigationLink value) selectable, one value or a Set; editing is the edit mode, which EditButton flips. ForEach's onDelete makes its rows deletable (a swipe, or edit mode's minus) and onMove makes them reorder with a grip, with moveItems to apply the move to an array. swipeActions, contextMenu and listRowBackground are modifiers on a row.

Size classes

useHorizontalSizeClass() is compact below 700 points of width (a phone) and regular from it (a tablet, a desktop window); mount and SwiftUIView follow the root's width as it changes. The views that adapt do it as on iOS: NavigationSplitView is two columns when regular (the sidebar's toggle button hides and shows the sidebar) and one stack when compact, which pushes the detail while a List(selection:) in the sidebar has a selection and clears it on back (lists without a selection, and anything a sheet, popover or inspector presents, have no say); .inspector is a trailing panel when regular and a sheet when compact; Table shows every column under sortable titles when regular and the first column when compact.

<NavigationSplitView
  sidebar={<List selection={[team, setTeam]}>{/* rows with a tag */}</List>}
  detail={<Table data={people} columns={columns} sortOrder={[order, setOrder]} />}
/>

Scrolling

List, LazyVStack and LazyHStack build a long ForEach a batch of rows at a time as they scroll into view. ScrollViewReader hands a proxy whose scrollTo(id, anchor) scrolls to the view marked scrollID={id}, building lazy rows up to it first. refreshable (on a List or ScrollView, or above one) runs an async action on a pull, with the spinner while it runs. scrollTargetBehavior="paging" or "viewAligned" on a ScrollView snaps, with scrollTargetLayout on the stack inside. Name a coordinate space with coordinateSpace="page" on the ScrollView (or any view), and a GeometryReader inside reads its frame there with proxy.frame({ named: 'page' }), updated as the page scrolls.

scrollTransition draws effects as a view leaves the visible area of its scroll view and comes back. The closure gets the effect to build on and the phase (topLeading, identity or bottomTrailing, with value -1, 0 or 1). By default the effect follows the scrolling, drawn partway while the view is partly out; configuration: 'animated' switches phases with an animation instead. containerRelativeFrame sizes a view against the scroll view's visible area, so a carousel of cards is:

<ScrollView axes="horizontal" contentMargins={['horizontal', 20]} scrollEdgeEffectStyle="hard">
  <HStack spacing={12}>
    {cards.map((card) => (
      <CardView
        key={card.id}
        card={card}
        containerRelativeFrame={{ axes: 'horizontal', count: 5, span: 2, spacing: 12 }}
        scrollTransition={(effect, phase) => effect.scaleEffect(phase.isIdentity ? 1 : 0.85).opacity(phase.isIdentity ? 1 : 0.4)}
      />
    ))}
  </HStack>
</ScrollView>

contentMargins pads the content inside the scroll views below (it scrolls under the margin). scrollEdgeEffectStyle (hard, soft, automatic, or ["hard", "top"] for one edge) and scrollEdgeEffectHidden set the iOS 26 edge effect where a list or scroll view runs under a bar. All three apply to the scroll view they are written on and pass down to the ones inside, as SwiftUI's environment does. LazyHGrid lays its cells into rows column by column, for a horizontal ScrollView.

Drawing and media

Shape takes a path function of the rectangle it is laid out in and draws a Path (move, addLine, addCurve, addArc, addRoundedRect…); UnevenRoundedRectangle and ContainerRelativeShape are built on it. Canvas hands its renderer a GraphicsContext (fill, stroke, text, images, transforms, opacity, clips, filters, gradients in its own coordinates), and TimelineView re-renders on a schedule, so a clock is a TimelineView around a Canvas. Views passed in the symbols prop ({ star: <Image systemName="star.fill" /> }) come back from resolveSymbol and draw with drawSymbol at a point, under the context's transform and opacity; they are real views laid over the canvas, so they draw above its other commands. useAVPlayer(url) is an AVPlayer (play, pause, seek, mute, volume, rate) that VideoPlayer shows in the page's <video>; the position is read or observed without re-rendering.

Mouse, keyboard and effects

A web app may have a mouse and a keyboard, so the modifiers SwiftUI keeps for iPad and Mac work here: onHover (a mouse entering and leaving the view's frame), help (a tooltip), pointerStyle (link, text, grabIdle… or any CSS cursor), keyboardShortcut on a Button (⌘ is Ctrl off Apple platforms) and onKeyPress (keys pressed while no text field has the focus). symbolEffect draws SF Symbols' effects (bounce, pulse, variableColor, rotate, wiggle, breathe, scale, appear), running while active or once per change of a value. visualEffect={(effect, proxy) => …} draws effects (offset, scale, rotation, opacity, blur, color adjustments) from the view's geometry without changing its layout; the proxy's frame('global') and bounds('scrollView') stay current as the page scrolls, so a parallax or a fade by position is one closure. matchedGeometryEffect with a namespace from useNamespace() moves a view into the frame of the one it replaces in an animated change. contentTransition="numericText" rolls a text's old value out and the new one in when it changes in an animated pass, upward or downward as the number grows or shrinks ({ numericText: { countsDown } } fixes the direction); the whole text rolls, not each digit. opacity and interpolate crossfade. sensoryFeedback vibrates where the device can.

Animation

withAnimation(animation, () => …) animates the pass a state change makes, and the animation modifier a view's changes when a value changes. PhaseAnimator moves its content through phases, each change animated; KeyframeAnimator plays tracks of keyframes (linear, cubic, spring, move) and renders its content with the values each frame.

Charts

Chart takes BarMark, LineMark, AreaMark, PointMark and RuleMark with their values in x and y: categories, numbers or dates. The component builds the scales and ticks the Swift Charts module builds (bands, nice numeric domains that include zero, calendar strides), groups lines and areas by series, colors foregroundStyleBy series from the chart palette with a legend, and places annotations.

Mixing with ordinary React

Both directions work, and they nest: a react-dom page can hold a SwiftUI island, and the island can hold ordinary React DOM inside one of its rows. The island page of the demo (island.html) does exactly that.

A SwiftUI tree inside a react-dom app

SwiftUIView, shown in Getting started: a component whose children are a SwiftUI tree, drawn in a box of the page.

React DOM inside a SwiftUI element

Host puts ordinary React DOM, with its own components, stylesheets and event handlers, inside a SwiftUI tree. Its children render with react-dom in a host element the layout engine sizes, so the DOM sits inside list rows, stacks and screens like any other view:

function Composer() {
  const [notes, setNotes] = useState('');
  return (
    <List>
      <Section header="Appearance">
        <LabeledContent label="Notes" value={`${notes.length} characters`} />
      </Section>
      <Section header="Composer" footer="A textarea and a counter, rendered by react-dom inside the list row.">
        <Host className="composer">
          <textarea value={notes} placeholder="Write something…" onChange={(e) => setNotes(e.target.value)} />
          <div className="count">{280 - notes.length}</div>
        </Host>
      </Section>
      <Section header="Map">
        <Host sizing="fill" frame={{ height: 160 }}>
          <MapView />
        </Host>
      </Section>
    </List>
  );
}

Typing in the textarea updates notes in the React component above, the SwiftUI row shows the count, and the row grows with the text.

  • Sizing. sizing="content" (the default) measures the DOM like text: as wide as the content wants, up to the width the parent proposes, and as tall as it is at that width. Whenever the content changes size (more lines, an image that loaded, a state change), the tree is laid out again around it. sizing="fill" takes the proposal, so a frame (or the parent's size) decides the box and the content fills it; height: 100% in the content reaches the box.
  • Styling. The content inherits the device's font and text color, and the iOS tokens are CSS custom properties it can use: var(--sb-color-secondary), var(--sb-color-accent), var(--sb-color-separator), the Dynamic Type sizes, and so on; they follow dark mode. className and style land on the wrapper the children render in.
  • Events. The DOM handles its own events. Pointer downs inside a host never start the renderer's gestures (no swipe, no drag), so text selection, scrolling inside the host and native controls work; the row around it still swipes when the pan starts outside the host.
  • Hooks. The content can use this package's hooks: useDismiss() from a sheet or a pushed screen, useNavigation() to push, useColorScheme().

A host is a React root of its own, so it needs the react-dom peer dependency, and React context does not cross into it by itself; see below. It survives StrictMode's simulated unmount and remount, as does SwiftUIView (island.html?strict in the demos runs under it).

Contexts across roots

SwiftUIView and Host each start a React root, and React context stops at a root boundary. This package's own contexts (the environment, navigation, dismiss, the view environment) are bridged automatically. For the page's own contexts, name them on the component and they are read on the outer side and provided again inside:

<SwiftUIView contexts={[ThemeContext, RouterContext]}>…</SwiftUIView>
<Host contexts={[ThemeContext]}>…</Host>

The list must be the same on every render of that component (each entry is read with a hook). Props, callbacks and external stores need no bridging.

How it works

  • src/host/: a react-reconciler host whose instances are protocol elements. After each React commit, HostTree.flush resolves two virtual kinds (a preference merges props into the enclosing navscreen or sheet; a hoist moves children to the enclosing screen or to the root, as .toolbar, .sheet and .alert do), diffs the result against the last pass and emits one render pass: creates, updates, inserts (parents before children), removes, commit. Events from the renderer reach the element's handlers and flush synchronously.
  • createSwiftUIRoot(sink) renders a tree into any ops sink; mount(screen, element) pairs a root with a Renderer from @swiftbrowser/web/src/renderer. withAnimation(() => setCount(1)) stamps the pass's commit with the animation, as withAnimation { } does.
  • src/modifiers.tsx applies the modifier props; src/components/ holds the components; src/components/host.tsx and src/island.tsx the two mixing components, over the renderer's host element kind and its island layout mode (docs/ops-protocol.md).
  • test/fidelity.test.tsx renders Examples/Fidelity's tab view through the host and compares the element tree with Examples/Fidelity/__snapshots__/fidelity.jsonl, the ops the Swift app emits for the same screens; test/extras.test.tsx covers the components beyond the Swift app.

What is covered

  • Layout: stacks, LazyVStack, LazyHStack, Spacer, Divider, ScrollView (with contentMargins and scroll edge effects), ScrollViewReader, Group, ForEach (with onDelete and onMove), Grid, LazyVGrid, LazyHGrid, ViewThatFits, GeometryReader (its size, its frame on screen or in a coordinateSpace named on an ancestor, the visible bounds of its scroll view, and its safe-area insets), custom Layout (with layoutValues, priorities, view spacing, explicit alignment guides and a cache), containerRelativeFrame, safeAreaInset.
  • Content: Text (with Text + Text runs, inline images, italic, underline, strikethrough, tracking), Image (symbols, catalog images with the assets option, remote images and AsyncImage), Label and its styles, LabeledContent, Link, Color as a view, shapes (built-in, custom paths, UnevenRoundedRectangle, ContainerRelativeShape) with fills, strokes and gradients, Canvas, TimelineView, VideoPlayer, Gauge, ProgressView, Swift Charts.
  • Controls: Button (styles, roles, sizes), Toggle, Picker, Slider, Stepper, TextField and SecureField (focus bindings, submit labels), TextEditor, DatePicker, ColorPicker, ShareLink, Menu, EditButton.
  • Containers: List (selection, edit mode, swipe actions, deletion, reordering, pull to refresh), Form, Section, Table, GroupBox, DisclosureGroup, ControlGroup, ContentUnavailableView.
  • Navigation and presentation: NavigationStack (value and destination links, a path binding, nested stacks flattened), NavigationSplitView, TabView/Tab, toolbars, search fields, sheets with detents, popovers (as sheets), alerts and dialogs, inspectors.
  • Interaction: onTapGesture, dragGesture, onLongPressGesture, contextMenu, draggable and dropDestination (in the app, and text or files from outside the page), onHover, help, pointerStyle, keyboardShortcut, onKeyPress, sensoryFeedback.
  • Animation and effects: withAnimation, animation, transitions, PhaseAnimator, KeyframeAnimator, matchedGeometryEffect, symbolEffect, contentTransition, visualEffect, scrollTransition, redacted placeholders, textCase.
  • Mixing: Host and SwiftUIView.

Not yet

  • contentTransition="numericText" rolls the whole text rather than only the digits that changed, and Canvas symbols always draw above the canvas's other commands.
  • scrollTransition takes no thresholds: the effect starts as soon as any of the view is out of the visible area and is complete when all of it is. containerRelativeFrame measures against the scroll view's whole visible area, content margins included, and its length closure is read as a linear function of the container's length.
  • The Swift-only bridges (SwiftData, URLSession, notifications, Core ML, StoreKit) have no React counterpart, by design: a web app uses the platform's own.
  • The ops protocol is the contract with the renderer and is versioned with it: this package imports the renderer's sources from @swiftbrowser/web/src/*.