What you can do
She already embeds the Development homepage hero from Development Integration. The hero is Text and Image today — two of the ten built-in cards: Text, Image, Video, Icon, Button, Divider, Embed, List, Accordion, Custom. She also has a BookingForm in her Next.js app. That form talks to her API and uses her auth. She wants it on the hero.
A marketer should be able to place it and set variant or seats without a RuleCMS enablement. She should not have to rebuild the form as Text plus Button, and she should not have to ship a palette package for one control that already lives in the app.
mounts is that handshake. Her developer registers BookingForm on RuleCMSWidget under a short name. The marketer drops Custom and types that same name. The published page looks the name up and renders her form. The composer always shows the dashed box — her site is where it appears.
Two everyday jobs:
- Put a host component on the hero. A booking form, a pricing calculator, a seat picker. The marketer places the slot and edits the knobs. You keep the code.
- Leave a design-system card as a library. A React hero that needs a composer preview and its own attributes is Component Libraries — a published package and a RuleCMS enablement. That is a different door.
Custom is one of the ten built-ins. It is not a library. It is not Embed — YouTube, Maps, and Calendly stay on that card.
| You want | Use |
|---|---|
| A component that already lives in her app, configured by a marketer, no RuleCMS enablement | Custom + mounts (this page) |
A click handler or other behaviour on that slot — onBooked, onClick | Custom Props and Click Handlers (componentProps, keyed by the Custom column's Component id) |
| First-class palette cards, a composer preview, and your own attribute editors | Component Libraries |
| YouTube, Vimeo, Google Maps, or Calendly | Embed |
| Hide the slot on phones | Column Hide — not a Custom setting |
How it thinks
The name is a map key, not code. She types booking-form. The live page does mounts['booking-form'] and renders whatever component you registered. There is no import(name), and there is no URL in the widget JSON. A typo is a dashed box. Publishing does not invent a registration.
RuleCMS never receives mounts. The composer cannot preview her form and cannot offer a list of names. The canvas is always the placeholder. That is the product, not a gap.
Here is the case that surprises people. She publishes the hero. The live production page still shows the dashed box. The name is spelled right. Production is still on a build that does not pass mounts. You have to publish the widget and deploy the app that has the map. Register the same names on every RuleCMSWidget that should resolve them. A widget you did not pass the map to cannot fill the slot.
RuleCMSWidgetServer can take mounts. That is the opposite of componentProps, which cannot — a function does not cross the server boundary. If BookingForm needs hooks or click handlers, mark it 'use client' and register that module. The samples live on Props and names.
The HTML Custom Element Embed cannot do this. The script tag has no way to receive a React component. A WordPress page that only loads that embed will keep the dashed box.
You need @rulecms/widget-react 22.16.0 or later and @rulecms/source-components-react 20.19.0 or later, plus 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.
Put Maya's BookingForm on the hero
She does this after the hero is on her local site from Development Integration: a dev. token and the draft widget-… key from Integrate. The Default Widget is Production-only. She created this hero in Development.
- Write (or keep)
BookingFormso it accepts the knobs a marketer should own. Treat that list as a public configuration surface. Keep secrets out of it. Event handlers do not come from the composer — pass those with componentProps at the Custom column's Component id.
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) {
// her existing form — talks to her API, uses her auth, her styles
return (
<form data-variant={variant} style={{ ['--accent' as string]: theme?.accent }}>
{/* … */}
</form>
);
}- Register it on every
RuleCMSWidgetthat should be able to render it. The key is the name she will type.
'use client';
import { RuleCMSWidgetProvider, RuleCMSWidget } from '@rulecms/widget-react';
import * as sourceComponents from '@rulecms/source-components-react';
import { BookingForm } from './BookingForm';
const token = process.env.NEXT_PUBLIC_RULECMS_DEV_TOKEN; // must start with "dev."
const widgetKey = 'widget-…';
export function HomepageHero() {
return (
<RuleCMSWidgetProvider
token={token}
libraries={{ default: sourceComponents }}
>
<RuleCMSWidget
publishedKey={widgetKey}
mounts={{ 'booking-form': BookingForm }}
/>
</RuleCMSWidgetProvider>
);
}Register every name the marketer might type, including a compact variant on phones:
mounts={{
'booking-form': BookingForm,
'booking-form-compact': CompactBookingForm,
}}- Give her a card she can follow. She has no schema and no autocomplete. The exact name, the props it reads, and the type of each prop — text, number, yes/no, JSON.
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.- She opens the homepage hero in the composer. On the left, stay on Components. Drop Custom. In the Modify drawer, type
booking-formin Component Name. Fill Props from the card — or leave them empty if the defaults are fine. - Publish the widget. Deploy the app that contains the registration. Refresh her site. The form appears inside the column box. The composer still shows the dashed box. That is a safe stop.
Point her at Custom Component for the editor side: what she types, what the box means.
Custom is a leaf. She cannot nest other cards inside the form. Prop values are in the published widget JSON — not secrets. Event names cannot be authored; use Custom Props and Click Handlers at that column's Component id.
What to read next
This page is the story and the first walkthrough. The prop types, the name grammar, and the surfaces that stay a dashed box live on the page below.
- Props and names — Text / Number / Yes/no / JSON, keys RuleCMS drops, the name grammar, unmatched names, what her component receives, server render, and why the HTML embed cannot.
- Custom Component — The marketer's page: what they type, what the box means.
- Custom Props and Click Handlers —
onBooked/onClickby the Custom column's Component id. - Component Libraries — A first-class palette card and a composer preview. Needs a RuleCMS enablement.
- Development Integration — Embed the Development hero first: the
dev.token and the draftwidget-…key from Integrate. - HTML Custom Element Embed — Cannot receive
mounts. Use@rulecms/widget-reactin a React host. - Publishing — Draft versus the snapshot a token fetches. Promote copies the draft; it does not publish.
How this fits
This page does not replace an embed. Development Integration is how she puts the hero on the page. This page is how a component she already owns lands in a Custom slot on that hero.
It is also not a library. Component Libraries adds a real palette card after we enable a package. Custom stays a typed name and a dashed box. Use this door when the component should stay in the app. Use a library when editors need a preview and attributes of their own.
It is also not extra props by column id. Custom Props and Click Handlers addresses onBooked at the Custom column's Component id. A mounted form can still receive handlers that way. Do not mix the two jobs.
The Default Widget is in Production. She built this hero in Development. Promote copies the draft to the next environment; it does not publish. When she publishes, she switches the app to a Staging or Production token — no dev. prefix — and that published key. The name booking-form does not change. Do not mix a dev. token with a published key, or a published token with a draft widget-… key.