The package you import from, and it renders nothing at all. It creates element objects, declares the hook and context APIs, and forwards every hook call to whichever renderer is currently mounting — the implementation lives there, not here.
import { useState } from 'react'react-reconciler
packages/react-reconciler
The algorithm itself, with no idea what a DOM is. It builds and diffs the fiber tree, calls your components, stores their hook state, and hands the resulting mutations to a host config you supply. It publishes as a package so you can write your own renderer.
const Renderer = Reconciler(hostConfig)
6 sub-nodes →scheduler
packages/scheduler
A priority queue and a yield loop, with nothing React-specific in it. It runs callbacks in slices and hands control back to the browser every few milliseconds, so a long render cannot sit on the main thread and block paint or input.
scheduleCallback(NormalPriority, work)
react-dom
packages/react-dom
The browser entry point — createRoot, hydrateRoot, createPortal, flushSync. It is a thin public surface over the reconciler wired to the DOM host config, which is why it is a separate install from react in the first place.
createRoot(document.getElementById('root'))
.render(<App />)react-dom-bindings
packages/react-dom-bindings
Everything that actually knows about the DOM: creating and patching nodes, deciding attribute versus property, and the synthetic event system — one delegated listener per root that replays the bubble along the fiber tree rather than the DOM one.
// one listener on the root container,
// dispatched down the fiber path
react-native-renderer
packages/react-native-renderer
The same reconciler pointed at native views instead of elements. It sits in this repo next to react-dom because that is the proof the split holds: swap the host config and the whole component model comes with you unchanged.
// same fibers, different host nodes
react-server
packages/react-server · react-server-dom-*
Server Components: a renderer whose output is a stream, not a tree of nodes. It serialises the rendered output into the Flight format and leaves holes where client components go, which the bundler binding fills with chunk references the browser can fetch.
renderToPipeableStream(<Page />, manifest)
react-dom/server
packages/react-dom/src/server
Classic server rendering: walk the tree once, push HTML strings into a stream, never diff anything. It also plants the markers hydrateRoot reads on the client so the first render can adopt the existing DOM instead of replacing it.
const stream = renderToPipeableStream(<App />)
The pieces every other package needs and none of them should own: the element and portal symbols, the feature flags each build is compiled against, and ReactSharedInternals — the single mutable slot through which react and a renderer find each other.
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols'react-is
packages/react-is
Element type checks, published so that libraries inspecting children can ask the same question React asks — isValidElement, isFragment, typeOf. The alternative is every library guessing at symbols it does not own.
isValidElement(node)
react-refresh
packages/react-refresh
Fast Refresh. A Babel transform registers each component under a stable identity, and this runtime swaps the implementation in place and re-renders, keeping the hook state of every component whose hook signature did not change.
// edit a component, keep its state
eslint-plugin-react-hooks
packages/eslint-plugin-react-hooks
The rules of hooks, enforced before the code runs. Hook state is matched by call order, so a hook behind an if silently reads the wrong slot — a class of bug the runtime cannot always catch, shipped as a lint rule from the same repo instead.
react-hooks/rules-of-hooks
react-hooks/exhaustive-deps