What this is for
You already have a React component in your application — a booking form, a pricing calculator, a seat picker. A marketer needs to place it on a RuleCMS page, hide it on phones, and change a variant or a count without you republishing the site every time.
mounts is that handshake. You register the component on RuleCMSWidget under a short name. They drop Custom on the canvas and type that same name. The published page looks the name up in your map and renders your component. The name is a map key, not a string that becomes code.
@rulecms/widget-react 22.16.0 or later, @rulecms/source-components-react 20.19.0 or later, and a name you will actually tell a human to type. Write the name down for them, with the props list. The composer has no autocomplete for this.When to use this — and when not to
| You want | Use |
|---|---|
| A component that already lives in your app, configured by a marketer, no RuleCMS redeploy | Custom + mounts (this page) |
| A click handler or other behaviour on one instance, including on a mounted component | Custom Props and Click Handlers (componentProps, keyed by column id) |
| First-class palette cards, composer preview, and your own attribute editors | Component Libraries — a published package and a RuleCMS enablement |
| YouTube, Vimeo, Google Maps, or Calendly | Embed — not a host component |
| Hide the slot on phones | Column Hide — not a Custom setting |
The handshake
Custom does not work unless you and the marketer agree on a string. RuleCMS never sees your component, so the composer cannot preview it and cannot offer a list of names. A typo on either side is a dashed box on the live page.
- You write the component in your app.
- You register it on every
RuleCMSWidget/RuleCMSWidgetServerthat should be able to render it. - You give the marketer a card: the exact name, the props it reads, and the type of each prop (text, number, yes/no, JSON).
- They drop Custom, type the name, fill the props, and publish.
- You deploy the app that contains the registration. Publishing the widget is not enough if production is still on a build that does not pass
mounts.
Step 1 — Write a component that accepts authored props
Treat the props list as a public configuration surface. Keep internal names and secrets out of it. Event handlers do not come from the composer — pass those with componentProps.
type BookingFormProps = {
variant?: 'full' | 'compact';
seats?: number;
showWaitlist?: boolean;
theme?: { accent?: string };
onBooked?: (id: string) => void;
};
export function BookingForm({
variant = 'full',
seats = 8,
showWaitlist = false,
theme,
onBooked,
}: BookingFormProps) {
// your existing form — talks to your API, uses your auth, your styles
return (
<form data-variant={variant} style={{ ['--accent' as string]: theme?.accent }}>
{/* … */}
</form>
);
}Unknown props are ignored by React. A marketer who adds a name you do not read will not break the page. A marketer who picks the wrong type will not break it either — RuleCMS drops a row that fails its declared type rather than guessing.
Step 2 — Register it on the widget
'use client';
import { RuleCMSWidgetProvider, RuleCMSWidget } from '@rulecms/widget-react';
import * as sourceComponents from '@rulecms/source-components-react';
import { BookingForm } from './BookingForm';
export function ClassPage() {
return (
<RuleCMSWidgetProvider
token={process.env.NEXT_PUBLIC_RULECMS_TOKEN}
libraries={{ default: sourceComponents }}
>
<RuleCMSWidget
publishedKey="…---widget-…"
mounts={{ 'booking-form': BookingForm }}
/>
</RuleCMSWidgetProvider>
);
}Register every name the marketer might type, including per-device variants:
mounts={{
'booking-form': BookingForm,
'booking-form-compact': CompactBookingForm,
}}The same map works on RuleCMSWidgetServer. Unlike componentProps, this is a component the renderer invokes, so it does not have to cross the Server Component boundary as a function prop of the visitor's. If your component needs hooks or click handlers, mark it 'use client' and register that module.
Step 3 — Give the marketer a card they can follow
They have no schema and no autocomplete. A short written card prevents the dashed-box support ticket.
Component Name: booking-form
(optional on phones: booking-form-compact)
Props
variant Text "full" or "compact"
seats Number how many seats to show
showWaitlist Yes / no true or false, exactly
theme JSON {"accent":"#0af"}
Do not put secrets in Props. They ship with the page.
Hide the slot on phones with Hide on the column, not with a prop.Point them at Custom Component for the editor side.
What the component receives
On a hit, r-mount renders a wrapper <div> that carries the column's box styles, then your component:
<div style={columnStyles} className={columnClassName}>
<BookingForm
{...widgetInstanceProps}
variant="compact"
seats={12}
showWaitlist={true}
theme={{ accent: '#0af' }}
/>
</div>Author props are never spread onto the wrapper. Your component is a child, not the box. Size, padding, and hide belong to the column.
widgetInstanceProps (published key, environment, and the rest of the instance bag) are passed first. Author props overwrite on collision. Do not publish a configuration name that clashes with an instance field you still need.
Prop types the composer can author
| Composer type | Stored as | Your component receives | Dropped when |
|---|---|---|---|
| Text | string | the string as typed | never, if the row is well-formed |
| Number | number | a finite number | the value is not a finite number (twelve, blank) |
| Yes / no | boolean | true or false | anything other than the exact strings true / false |
| JSON | json | the parsed value (object, array, number, …) | JSON.parse throws |
Keys that are never passed
RuleCMS drops these from the authored list, in the editor and at render. They cannot become behaviour.
- Event names: anything matching
onClick-styleonX, plus a set of HTML events such asonclickandonload - React reserved:
key,ref,children - HTML injection:
dangerouslySetInnerHTML - Prototype chain:
__proto__,constructor,prototype
To pass onBooked or onClick, use componentProps addressed at the Custom component's column id:
<RuleCMSWidget
publishedKey={publishedKey}
mounts={{ 'booking-form': BookingForm }}
componentProps={{
[BOOKING_COLUMN_ID]: { onBooked: openConfirmation },
}}
/>componentProps still cannot go on RuleCMSWidgetServer. Fetch on the server and render from a client component, the same pattern as the Custom Props guide.
The name grammar
The composer and the renderer accept the same pattern: start with a letter, then letters, digits, hyphens, or underscores, up to 64 characters.
booking-form ok booking_form ok BookingForm ok bookingForm ok — and different from booking-form 2fa-form refused (leading digit) booking form refused (space) booking.form refused (dot)
Pick one style and keep it. Hyphenated lowercase is the easiest to read over a Slack message and the hardest to camelCase by accident.
What you will see, and what they will see
| Surface | What renders |
|---|---|
| Composer canvas | Always the dashed placeholder. RuleCMS never receives mounts. |
| Your app, name matches a key | Your component, inside the column box. |
| Your app, name missing or misspelled | The placeholder, showing the name they typed. Outside production, the console warns once and lists the keys you registered. |
Your app, you passed no mounts | The placeholder. Publishing does not invent a registration. |
| HTML script embed | Always the placeholder. The IIFE does not contain your components and has no way to receive a ComponentType. |
r-mount: no host component registered as "booking-form". Registered names: pricing-calc.
Server-rendered pages
Pass the same map to RuleCMSWidgetServer. Your component is rendered on the server if it can be.
import { RuleCMSWidgetServer } from '@rulecms/widget-react/server';
import * as sourceComponents from '@rulecms/source-components-react';
import { BookingForm } from './BookingForm';
export default async function Page() {
return (
<RuleCMSWidgetServer
publishedKey={publishedKey}
token={token}
libraries={{ default: sourceComponents }}
mounts={{ 'booking-form': BookingForm }}
/>
);
}@rulecms/widget-react in a React (or Next.js) host. A WordPress page that only loads the script tag will keep showing the dashed box.Use cases, worked
A booking form the marketer can turn on and off
Register booking-form. They drop Custom, type that name, and use column Hide to take it off phones or off the page during a studio closure. No prop required if your defaults are fine. To let them pick a class series without a deploy, add a text prop they type, for example series.
A calculator whose numbers they own
Register pricing-calc. They set basePrice (number), currency (text), and showDeposit (yes/no). You keep tax logic and the checkout call in the component. When the price changes, they edit the widget and publish. You do not.
A compact variant on phones
Register both names. They uncheck "Same value for all resolutions" on Component Name and type booking-form-compact for phone, booking-form for desktop. Two components, one slot.
A handler the composer must not author
They configure variant in Props. You pass onBooked with componentProps at that column's id. The marketer never types a function. The live page still opens your drawer.
Things worth knowing
| Question | Answer |
|---|---|
| Can the composer preview my component? | No. The dashed box is the product. RuleCMS does not have your bundle. |
| Is the name loaded as code? | No. It is mounts[name]. A miss is a placeholder. There is no import(name) and no URL in the widget JSON. |
| Do I have to register the map on every widget on the page? | On every RuleCMSWidget that should be able to resolve those names. A widget you did not pass the map to cannot fill the slot. |
| What if they type a prop I do not read? | Nothing happens. Same as an unknown HTML attribute on a DOM node you do not look at. |
| Can they pass children? | No. Custom is a leaf. Nested cards inside a host component are not shipped. |
| Are prop values secret? | No. They are in the published widget JSON. Anyone can read them. Do not put tokens or passwords there. |
Related docs
- Custom Component — the marketer's page: what they type, what the box means
- Component Name and Props
- Custom Props and Click Handlers —
onClick/onBookedby column id - Component Libraries — when you need a real palette card and a composer preview
- Development Integration — live Development drafts on your site
- HTML Custom Element Embed — cannot receive
mounts