Widget Image

Building a Ractro element

Building a Ractro element

A Ractro element is ONE PHP class with two required methods — config() and render(). No JSON manifest, no build step, no registration file to edit.
Everything on this page was checked against the running engine, not written from memory.

1. Register it

add_action(‘ractro/widgets/register’, function ($mgr) { $mgr->register_widget(new Ractro_Widget_Hello()); });
Or register a whole folder at once with $mgr->register_widget_path(__DIR__ . ‘/widgets/’). Filenames follow class-ractro-widget-<slug>.php.

2. config()

Keys: id (the block type), name (panel label — NOT ‘title’, which silently falls back to the id), icon (any lucide-react name), category, order, and tabs.
tabs maps ‘content’ / ‘style’ / ‘advanced’ straight to a FLAT list of controls. There is no section level — group visually with ‘popover’ on a control.

3. Controls and styling

Types available: color, select, sizeunit, slider, text, switch, dimensions, number, iconselect, boxshadow, alignment, textarea, icon, image, repeater, background, code.
Add ‘selectors’ and the engine compiles the CSS for the editor canvas AND the published page from one declaration. {{WRAPPER}} becomes the element’s own class; {{VALUE}} is the setting.
Responsive and states come free: a control named gap also accepts gap_tablet, gap_mobile and gap:hover. You do not declare them.

4. Atomic rendering — one DOM node, not two

Return true from supports_atomic_root() and name your root in atomic_root_class(), then emit $this->root_attrs(‘your-class’) inside your outermost tag. 140 of the 151 shipped elements do this.
Every return path that emits an element must call root_attrs(), including editor-only notices. Never leave a second class attribute on that tag — the browser keeps the first and silently drops the rest.

5. The trap that will bite you

If your element ships JavaScript, never resolve your own root with el.querySelector(‘.your-root’). Once the element IS that root, it is not its own descendant, the query returns null, and your handler silently does nothing — correct markup, correct CSS, dead behaviour.
Use window.ractroFrontend.root(el, ‘your-root’) instead. It returns the element itself when it already carries the class.

6. Before you ship

wp ractro lint — schema contract across every element. wp ractro parity — editor and front end agree. dev/atomic-check.php — renders every atomic element through the real engine and catches what a PHP lint cannot see.