Components
Role
src/themes/reactwp/js/components/ contains the reusable React building blocks shipped with the starter.
These components are intentionally small. They are not a design system by themselves. Their job is to cover the core patterns ReactWP needs often:
- internal links with route prefetching
- button rendering
- rich text output
- editorial content blocks
- media placeholders for the loader runtime
- shell-level header and footer mounting
Available Components
AppLink.jsxAudio.jsxButton.jsxContents.jsxFooter.jsxHeader.jsxImage.jsxRichText.jsxVideo.jsxWrapper.jsx
Component Categories
Navigation and Actions
AppLinkButton
Content Rendering
ContentsRichTextWrapper
Media Placeholders
ImageVideoAudio
Shell Components
HeaderFooter
AppLink
AppLink is the base component for internal navigation.
It wraps React Router's Link, but also prefetches the next route on hover and focus through the loader runtime.
Use it when:
- you are linking to an internal route
- you want React navigation and transitions
- you want route prefetching
Useful props:
todata-routerupdateHashonMouseEnteronFocus- any normal link props such as
className,target, orrel
to can be:
- a string path
- a React Router location-like object with
pathname,search, andhash
If data-router="false" or data-router={false} is passed, AppLink falls back to a normal <a> instead of React Router. In that mode it does not use the route prefetch behavior.
Same-page anchor links scroll through ReactWP's scroller. By default, updateHash is true, so a normal click also adds the target hash to the browser URL and history. Set updateHash={false} when the scroll is an interface convenience that should not change the visible URL or create a history entry. The rendered link keeps its real href, so copying the link and using a modified click still preserve the anchor destination.
Example:
import AppLink from '../components/AppLink';
const Example = () => {
return <AppLink to="/">Home</AppLink>;
};
Example without router interception:
<AppLink to="/wp-admin/" data-router={false}>
Open WordPress Admin
</AppLink>
Same-page scroll without changing the URL:
<AppLink to="#pricing" updateHash={false}>
View pricing
</AppLink>
Button
Button is the main action component.
It chooses the correct underlying element automatically:
- no
toand nohref-> renders a<button> - external
hrefortosuch ashttps://,mailto:, ortel:-> renders a normal<a> - internal
toorhref-> rendersAppLink
Useful props:
tohreftextchildrenbeforeaftervariantupdateHashclassName- any normal button or link props such as
type,target,rel, ordata-router
Important defaults and behavior:
variantdefaults toprimarytextis used when present, otherwisechildrenbecomes the main labelbeforeandafterrender inside.button__beforeand.button__after- when
Buttonrenders an internalAppLink, it forwardsupdateHash - string values in
before,after,text, orchildrenare rendered as escaped JSX text, not interpreted as HTML - forwarded DOM props are filtered by
sanitizeDomProps
Examples:
<Button onClick={handleClick}>Open modal</Button>
React supplies the click event to onClick. Use event.currentTarget when GSAP needs the actual button element:
<Button
text="Open panel"
onClick={(event) => {
gsap.to(event.currentTarget, { scale: 0.98, duration: 0.2 });
}}
/>
event.target can be a nested label or decoration inside the button. currentTarget is the element that owns the handler.
React removes its component event handlers when the component unmounts. Clean up timelines, ScrollTriggers, timers, observers, or manually attached native listeners that the handler creates; the JSX onClick itself does not need a manual kill.
<Button to="/contact/" variant="primary">
Contact us
</Button>
<Button
href="https://example.com"
before="Read"
after="now"
>
External article
</Button>
<Button href="/wp-admin/" data-router={false}>
Open WordPress Admin
</Button>
<Button href="#pricing" updateHash={false}>
View pricing
</Button>
Contents
Contents is a convenience content block for common editorial layouts.
It can render:
- an uptitle
- a title
- a subtitle
- text
- a list of buttons
Useful props:
uptitletitlesubtitletextbuttonstitleTagclassName- other safe DOM props you want forwarded to the outer
.contentsnode
Important defaults and behavior:
titleTagdefaults toh2- if every content prop is empty,
Contentsreturnsnull textis rendered throughWrapper, not directly throughRichText- string
textis escaped JSX text; pass a React node when structured markup is needed buttonsis mapped throughButton- each button object can use either
toorurl new_tabis converted totarget="_blank"whentargetis not already provided
titleTag is useful when the same content pattern needs different document semantics.
Examples:
<Contents
uptitle="ReactWP"
title="Project overview"
subtitle="Start from a clean baseline"
titleTag="h1"
text="Start by customizing your templates, site settings, and frontend runtime."
/>
<Contents
title="Section title"
titleTag="h3"
text="This can also be a subsection inside a longer page."
/>
<Contents
uptitle="ReactWP"
title="Project overview"
subtitle="Start from a clean baseline"
text="Start by customizing your templates, site settings, and frontend runtime."
buttons={[
{
text: 'Open home',
to: '/'
},
{
text: 'Open admin',
href: '/wp-admin/',
data-router: false,
new_tab: true
}
]}
/>
Button objects are forwarded to Button.jsx. In practice, the most useful keys are:
texttourlhreftargetnew_tabbeforeaftervariantupdateHashdata-router
RichText
RichText is ReactWP's transformed path for supported WordPress HTML. It parses a string with html-react-parser, removes unsupported nodes, filters attributes, validates URL-bearing values and srcset, and normalizes links that open a new tab.
Use it when React must transform the HTML tree during rendering, for example to remove nodes, change attributes, or rebuild supported markup. The parser is not a general-purpose sanitizer, so WordPress/ACF/API HTML must still be sanitized according to the backend content contract.
If value is already a React node, it is rendered directly inside a <div>.
Important behavior:
- falsy
valuereturnsnull classNamedefaults to an empty string- the shipped component accepts only
valueandclassName; it does not forward arbitrary props - string input is bounded and limited to the tags and attributes supported by the component
Example:
<RichText
className="copy"
value="<p>This content comes from WordPress or ACF.</p>"
/>
For backend-sanitized HTML that must remain unchanged and needs no React-level transformation, prefer a small explicit rendering boundary:
const SanitizedHtml = ({ html = '', className = '' }) => (
<div
className={className}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
Only pass HTML that the backend contract has already sanitized to this boundary.
Wrapper
Wrapper renders a custom <rwp-wrap> element around value.
Useful props:
value- other safe DOM props you want forwarded to
<rwp-wrap>
Example:
<Wrapper value={text} />
This is mainly useful when the project styles or scripts rely on that wrapper element.
String values are rendered as escaped JSX text. If you do not need the custom wrapper behavior, use ordinary JSX for plain text, an explicit unchanged-HTML boundary for already sanitized HTML, or RichText when React-level HTML transformations are required.
Image, Video, and Audio
These components are placeholder containers, not classic media tags with src props.
They render the shell expected by the loader runtime:
Image->.img-container > .inner-img > .imgVideo->.video-container > .inner-video > .videoAudio->.audio-container > .inner-audio > .audio
Useful props:
className- filtered DOM props such as
id,data-*, oraria-* - forwarded
ref
Example:
<Image className="hero-media" />
These components make the most sense when your project uses ReactWP's critical or non-critical media pipelines.
They are useful because the loader can swap the placeholder with the real media node later, after preloading.
Header
Header is a shell-level component rendered from React, but mounted outside the smoother through a portal.
Useful props:
showclassNamemountId- other safe DOM props you want forwarded to the final
<header>node
Important behavior:
- if
showisfalse, it renders nothing - if
showistrue, it portals into the element identified bymountId - default
mountIdisapp-header - the mount node should live outside
#pageWrapperand#pageContent - if the mount node is missing, it falls back to
document.body
Important limitation of the shipped component:
- the default
Header.jsxis only an empty shell - it does not render navigation, site data, or branding on its own
- extra props pass through
sanitizeDomPropsbefore they reach the final<header>element
If you want to pass navigation, site, or other runtime data, edit Header.jsx to consume those props explicitly. Do not rely on the DOM-prop filter as an application-data transport.
Example shell usage:
<AppShell
showHeader={true}
headerProps={{
className: 'site-header',
mountId: 'app-header'
}}
>
{children}
</AppShell>
If your header needs menu data, a more realistic pattern is:
import { sanitizeDomProps } from '../inc/domProps';
const Header = ({
show,
className,
mountId = 'app-header',
navigation = {},
...domProps
}) => {
if(!show){
return null;
}
const safeDomProps = sanitizeDomProps(domProps);
return createPortal(
<header className={className} {...safeDomProps}>
<Navigation navigation={navigation} />
</header>,
document.getElementById(mountId)
);
};
If you use a custom mountId, the matching node must exist in the PHP markup:
<div id="my-header-mount"></div>
For the shell structure that makes this work, see Theme Shell and Scroll.
Footer
Footer is simpler than Header.
Useful props:
showclassName- other safe DOM props you want forwarded to the
<footer>node
Important behavior:
- if
showisfalse, it renders nothing - if
showistrue, it renders an empty<footer>shell - like
Header, the shipped version does not render any actual content by itself - forwarded DOM props pass through
sanitizeDomProps
Example:
<AppShell
showFooter={true}
footerProps={{
className: 'site-footer'
}}
>
{children}
</AppShell>
In practice, most projects will customize Footer.jsx quickly.