Elementor Widget Builder

Build an Elementor widget from a control list: each control's type decides its tab, its render code and whether it becomes CSS through a selector.

Live output

Enable JavaScript to customise; default output below.

The label in the Elementor panel.

Stored in every page that uses the widget. Changing it later orphans those pages.

Every element gets this plus its own suffix, which is what the style selectors target.

general unless your add-on registers a category of its own.

Wrapper tag
More options Show

Comma separated. What the panel search matches on besides the title.

Live preview class-my-widget.php
<?php
/**
 * The "Feature Card" Elementor widget.
 *
 * Loaded by the add-on only once Elementor is running, so the parent class
 * is always available by the time this file is read.
 */

defined( 'ABSPATH' ) || exit;

class Feature_Card_Widget extends \Elementor\Widget_Base {

	/**
	 * The name stored in every page that uses the widget. Changing it later
	 * orphans those pages.
	 *
	 * @return string
	 */
	public function get_name() {
		return 'feature-card';
	}

	/**
	 * The label in the panel.
	 *
	 * @return string
	 */
	public function get_title() {
		return esc_html__( 'Feature Card', 'my-addon' );
	}

	/**
	 * The panel icon.
	 *
	 * @return string
	 */
	public function get_icon() {
		return 'eicon-info-box';
	}

	/**
	 * Which panel category it appears in.
	 *
	 * @return array
	 */
	public function get_categories() {
		return array( 'general' );
	}

	/**
	 * What the panel search matches on, besides the title.
	 *
	 * @return array
	 */
	public function get_keywords() {
		return array( 'card', 'feature' );
	}

	/**
	 * Declares the controls.
	 */
	protected function register_controls() {
		$this->start_controls_section(
			'section_content',
			array(
				'label' => esc_html__( 'Content', 'my-addon' ),
				'tab'   => \Elementor\Controls_Manager::TAB_CONTENT,
			)
		);

		$this->add_control(
			'icon',
			array(
				'label' => esc_html__( 'Icon', 'my-addon' ),
				'type'  => \Elementor\Controls_Manager::ICONS,
			)
		);

		$this->add_control(
			'title',
			array(
				'label'   => esc_html__( 'Title', 'my-addon' ),
				'type'    => \Elementor\Controls_Manager::TEXT,
				'default' => 'Fast by default',
			)
		);

		$this->add_control(
			'body',
			array(
				'label'   => esc_html__( 'Body', 'my-addon' ),
				'type'    => \Elementor\Controls_Manager::TEXTAREA,
				'default' => 'A sentence about it.',
			)
		);

		$this->add_control(
			'boxed',
			array(
				'label'        => esc_html__( 'Boxed', 'my-addon' ),
				'type'         => \Elementor\Controls_Manager::SWITCHER,
				'return_value' => 'yes',
				'default'      => 'yes',
			)
		);

		$this->end_controls_section();

		$this->start_controls_section(
			'section_style',
			array(
				'label' => esc_html__( 'Style', 'my-addon' ),
				'tab'   => \Elementor\Controls_Manager::TAB_STYLE,
			)
		);

		$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}};',
				),
			)
		);

		$this->end_controls_section();
	}

	/**
	 * Front end output.
	 *
	 * Settings are whatever the editor typed, so every value is escaped
	 * for the place it lands in. Style controls are absent here on
	 * purpose: Elementor writes them as CSS from their selectors.
	 */
	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 ) ) );

		if ( ! empty( $settings['icon']['value'] ) ) {
			echo '<span class="feature-card__icon">';
			\Elementor\Icons_Manager::render_icon( $settings['icon'], array( 'aria-hidden' => 'true' ) );
			echo '</span>';
		}

		printf( '<div class="feature-card__title">%s</div>', esc_html( $settings['title'] ) );

		printf( '<div class="feature-card__body">%s</div>', wp_kses_post( $settings['body'] ) );

		echo '</div>';
	}
}

Output is valid and updates as you type.

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

  1. 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.
  2. Add one row per control. The id is the array key you read in render, the label is what the editor sees.
  3. 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.
  4. Use the element suffix to say which part of the widget a control belongs to. title gives an element with the base class plus __title, and a style control aimed at title writes its CSS there.
  5. For a select, put the options in the default field, comma separated. The first is the default, and each becomes a slug.
  6. 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 url key 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_control and 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?
In your add-on plugin, usually 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?
Add the controls you want grouped and split the generated 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?
Every control you added is a content type. Style types are colour, slider and typography; those are what Elementor puts on that tab.
Can I add responsive controls?
Yes, by changing 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?
That needs a 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.

From the people who built this tool

Master Addons for Elementor

The widgets Elementor leaves out, built to stay fast: advanced tabs and accordions, mega menus, tables, forms and the extensions around them.

See Master Addons Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.