Theme Shell and Scroll
Role
ReactWP's theme shell is the fixed markup and container structure that the frontend runtime expects.
This part matters because:
- the loader needs a real DOM node before React mounts
- the smoother needs stable wrapper and content IDs
- portal-mounted UI such as the header must know where to render
Main Files
src/themes/reactwp/template/header.phpsrc/themes/reactwp/template/footer.phpsrc/themes/reactwp/template/functions.phpsrc/themes/reactwp/js/inc/AppShell.jsxsrc/themes/reactwp/js/inc/Scroller.jssrc/themes/reactwp/js/components/Header.jsx
Shell Markup
The default shell currently includes:
#reactwp-bootstrap#loader#app-header#viewport#pageWrapper#pageContent#app
At a high level:
#reactwp-bootstrapholds the initial JSON payload#loaderis the first-load overlay#app-headeris the default portal mount for the React header#viewportis the outer transition target#pageWrapperand#pageContentare the smooth-scroll wrapper and content nodes#appis where React mounts
Smooth Scroll Structure
Scroller.js creates ScrollSmoother with:
wrapper: '#pageWrapper'content: '#pageContent'
That means #pageContent is the transformed scroll layer.
Anything that should stay fixed outside that transformed layer should not live inside #pageContent.
Why The Header Uses A Portal
Header.jsx is rendered from React, but it portals into #app-header.
That keeps the header:
- controlled by React
- outside the smoother
- suitable for fixed positioning and shell-level UI
Custom Header Mounts
Header.jsx accepts mountId.
If you change it, the matching node must exist in the PHP shell markup.
That mount node should stay outside:
#pageWrapper#pageContent
Otherwise the header becomes part of the smoother layer and loses the main benefit of the portal.
Footer Behavior
Footer.jsx is different from Header.jsx.
The footer stays inside the app shell and scrolls with page content by default.
Shell Lifecycle Keys
useRouteTransition() exposes headerKey and footerKey. The current hook returns the same route generation for both, while AppShell namespaces them as header-* and footer-* before assigning React keys. The separate prop names let each shell component evolve independently and the prefixes keep sibling React keys unique.
Use those keys when a persistent shell component owns route-dependent GSAP or ScrollTrigger setup and must remount after the next route's enter phase. Header and footer keys are separate even if they advance from the same transition event.
Do not use a raw route path as the key for multiple sibling shell nodes. Duplicate sibling keys can cause React to duplicate or omit component instances.
Scroller API
Import the facade:
import { scroller } from '../inc/Scroller';
Available operations:
init()andkill()refresh()scrollTo(target, smooth)jumpToTop()lock()andunlock()
The facade works with ScrollSmoother and its native-scroll fallback. Reduced-motion users do not receive a smoother instance, so direct calls that assume window.gscroll exists are fragile.
Locks are reference-counted. Every lock() should have a matching unlock(), normally in a React effect cleanup.
useEffect(() => {
scroller.lock();
return () => scroller.unlock();
}, [route.key]);
Prevent Scroller Initialization
scroller.kill() removes an existing smoother; it does not prevent the route hook from calling scroller.init() on application mount.
If a project must never initialize ScrollSmoother, guard or remove the scroller.init() call in useRouteTransition.js as a project-level runtime change. Keep the facade for native scrollTo, refresh, and lock fallback behavior.
Critical Shell Styling
functions.php prints a small amount of inline CSS in wp_head.
That critical shell CSS exists so the user can immediately see:
- the page background
- the loader
- the minimum shell sizing
before the compiled theme stylesheet is fully applied.
The full theme styling still comes from the compiled CSS asset.
The shell markup is a runtime contract. If a project renames one of these IDs, update every corresponding PHP, JavaScript, loader, and smoother reference together.