Build an Elementor widget by listing its controls. The type of each one decides which tab it lands on, how it is escaped, and whether it becomes CSS instead of markup.
How to use
- Name the widget. The widget name goes into every page that uses it, so settle on it before you ship; the class name and base CSS class follow from it.
- Add one row per control. The id is the array key you read in render, the label is what the editor sees.
- Pick the type, and let it place the control. Text, textarea, number, select, switcher, media, URL and icons are content; colour, slider and typography are style.
- Use the element suffix to say which part of the widget a control belongs to.
titlegives an element with the base class plus__title, and a style control aimed attitlewrites its CSS there. - For a select, put the options in the default field, comma separated. The first is the default, and each becomes a slug.
- Copy the class into your add-on’s
widgets/folder and register it. The Elementor Add-on Plugin Generator writes the plugin that does the registering.
Example
Five controls, three of them content and two style, produce a widget whose render method touches only the content ones:
protected function render() {
$settings = $this->get_settings_for_display();
$classes = array( 'feature-card' );
if ( 'yes' === $settings['boxed'] ) {
$classes[] = 'feature-card--boxed';
}
printf( '<div class="%s">', esc_attr( implode( ' ', $classes ) ) );
printf( '<div class="feature-card__title">%s</div>', esc_html( $settings['title'] ) );
echo '</div>';
}
The colour control never appears there. It writes CSS through its selector instead, which is why a Style tab control needs no render code at all:
$this->add_control(
'title_color',
array(
'label' => esc_html__( 'Title colour', 'my-addon' ),
'type' => \Elementor\Controls_Manager::COLOR,
'selectors' => array(
'{{WRAPPER}} .feature-card__title' => 'color: {{VALUE}};',
),
)
);
Pitfalls
- A style control with render code is the most common mistake in custom widgets. Elementor already writes its value as CSS from the selector, so echoing it again means two sources of truth and inline styles that fight the stylesheet.
- A slider carries a number and a unit as separate keys. Its selector needs both tokens,
{{SIZE}}{{UNIT}}, and a size units list, or the CSS comes out unitless and is ignored. - The widget name is stored in page content. Renaming it leaves every existing page with a widget Elementor cannot resolve, and the settings are unreachable from the editor.
- Settings are editor input, not developer input. A textarea gets post-level filtering, a URL gets URL escaping, a media field is an array and its
urlkey can be empty on a fresh insert. - A switcher is a flag, so it belongs in the wrapper’s class list rather than in the output. The same is true of a select: it is a variant, not content.
- Typography is a group control. It goes in with
add_group_controland takes a selector, not a property, and its name becomes the prefix of several generated controls. - A control id has to survive being a PHP array key and a CSS class fragment. Ids that differ only by punctuation collapse into the same key, which this tool rejects rather than letting one control overwrite the other.
- Icons from the newer control are arrays, and rendering them by hand loses SVG support. The generated code uses the icons manager, which handles both font icons and uploaded SVGs.
Compatibility
The controls used here are available in Elementor 3.0 and newer, and the icons control needs 2.6 or newer. get_settings_for_display has been the right way to read settings since Elementor 1.0. Elementor Pro is not required. The generated class assumes it is loaded only when Elementor is running, which is what the add-on scaffold arranges. The tool runs entirely in your browser.
Frequently asked questions
Where do I put the file?
widgets/class-<name>.php, required inside the plugin’s Elementor check and registered on the widget registration hook.How do I add a second section?
start_controls_section call in two. The tool writes one content and one style section, which covers most widgets.Why is my Style tab empty?
Can I add responsive controls?
add_control to add_responsive_control for the control you want per-device. Elementor then writes the breakpoint CSS from the same selector.What about a live preview in the editor?
content_template method written as an underscore template. It is optional: without it Elementor renders the widget through PHP, which is slower in the editor but correct.