Make WordPress Sexy Again: how we built a web design system with custom Elementor widgets

letvar
7 min read4 days ago

--

Today we are going to talk about something seemingly boring and unsexy in 2024: WordPress.

We recently built the website for the San Marino pavilion at Expo 2025 Osaka, sanmarinoexpo.com, as a result of a partnership with Studio JAM and the General Commissariat of the Sammarinese Government for Expo 2025, as explained here.

The full website is finally available and provides all the information about the San Marino participation in Osaka. Today we want to give a few insights into the behind-the-scenes of its implementation.

Choosing technologies

We had two options: proceed with Magnolia CMS in a state-managed server environment, or take over full management of the site and choose the most appropriate technologies.

We chose the latter option for more freedom and control, and opted for WordPress (and Elementor) because of the better knowledge about it on both our side and on the side of the people who will use it, especially for a website with a manageable number of pages.

…although at first glance developing for WordPress in 2024 may not seem like the sexiest thing in this world.

It all started with a Figma file…

As usual in such cases, everything starts with a Figma file prepared by Studio JAM with all the pages to be created, about fifteen of them.

We then identified the most frequently used major interface components and isolated them from the rest: buttons, images, titles, call-to-actions, sponsor blocks, event blocks, and articles, as well as other minor components.

As good nerds, the watchword is reusability: it would not make sense to continuously duplicate sections and components, so we decided to build a kind of web design system called Goldenrod that would include those most frequently used, along with Elementor’s built-in tools for managing common typography and color settings.

Goldenrod City (Fiordoropoli, it.) is Osaka’s counterpart in Johto, the region of Pokémon Gold, Silver and Crystal. (Yes, Pokémon regions match 1:1 with their Japanese counterparts in the real world)

We quickly ran into a limitation of Elementor, namely that of being able to use dynamic templates, but with variables limited to dynamic fields provided by WordPress or Advanced Custom Fields (ACF). As far as we know, there is no way to create customizable widgets natively at the UI level without some programming.

We therefore created Elementor templates for the components that are part of the WordPress loop (articles and events), while we built custom widgets for the remaining components, following the Elementor developer documentation.

The elephpant in the room

Building widgets for Elementor may seem complex at first glance, but in fact it is not. By structuring the code, it is possible to separate each widget and inherit some basic parameters from it.

We are on WordPress, so in the moors of PHP, ergo this is the (programming) language we will also use for WordPress widgets.

Registering widgets and categories

To add custom widgets in Elementor, simply create a standard WordPress plugin. The entry point can have any name (e.g., goldenrod.php) and within it can have associated metadata that will be visible on the WordPress Plugins page, for example:

/**
* Plugin Name: Goldenrod Widgets
* Plugin URI: https://www.letvar.io
* Description: Japanese widgets ready for use in Elementor.
* Version: 1.0
* Author: letvar
* Author URI: https://www.letvar.io
* Text Domain: goldenrod
*
* License: (C) 2024 letvar
* Requires Plugins: elementor
*/

We have created a category to associate our custom widgets with. This way, they can appear in Elementor as a dedicated category, called Goldenrod:

function add_goldenrod_category( $elements_manager ) {
$elements_manager->add_category(
'goldenrod',
[
'title' => esc_html__( 'Goldenrod', 'textdomain' ),
'icon' => 'fa fa-ellipsis-vertical',
]
);
}
add_action( 'elementor/elements/categories_registered', 'add_goldenrod_category' );

Creating our first widget

When creating the widgets, we tried to abstract the basic properties, in this case the category and keywords, that can help in the search. Therefore, we created an abstract class named Goldenrod_Base, from which the concrete widgets will inherit:

abstract class Goldenrod_Base extends \Elementor\Widget_Base
{
public function get_categories()
{
return ['goldenrod'];
}

public function get_keywords()
{
return ['expo', 'osaka', 'san', 'marino'];
}

}

After that, we create our first real widget that concretizes Goldenrod_Base: in this case it is a component for describing thematic weeks of Expo consisting of a kicker, a title, a subtitle and an image. We called it Goldenrod_Week.

Let’s look at the first part of the file. We can see the function get_name() which returns an identifier of the widget, get_title() for the title, and get_icon() for the identifier of one of the Elementor icons (you can find the full list here).

class Goldenrod_Week extends Goldenrod_Base {

public function get_name() {
return 'goldenrod_week';
}

public function get_title(): mixed {
return esc_html__( 'GR Week', 'goldenrod' );
}

public function get_icon() {
return 'eicon-calendar';
}

// To be continued...

This is how the widget will appear in the widget list, based on the values we just wrote down:

Controls, controls, controls

As we explained earlier, we need four variables within our widget: a kicker, a title, a subtitle, and an image.

For the first two, given the small amount of text required, we can use a TEXT type control , while for the subtitle — which can span multiple lines — it is better to use a TEXTAREA or possibly an advanced text editor, i.e. WYSIWYG (What You See Is What You Get). The control types available in Elementor are listed here.

For images, however, you need to use a control of type MEDIAthat can show media from the WordPress library.

If you need to fill fields with dynamic WordPress tags, you can set the 'dynamic' value to'active' => true. By clicking on the corresponding icon in the Elementor editor, the field will be filled with the associated dynamic tag. There are a variety of parameters available for each type of control, so my advice is to consult the Elementor developer documentation.

Here is how we implemented the controls:


protected function _register_controls() {
$this->start_controls_section(
'section_content',
[
'label' => __( 'Content', 'goldenrod' ),
]
);

$this->add_control(
'kicker',
[
'label' => esc_html__('Kicker', 'goldenrod'),
'type' => \Elementor\Controls_Manager::TEXT
]
);

$this->add_control(
'headline',
[
'label' => esc_html__('Headline', 'goldenrod'),
'type' => \Elementor\Controls_Manager::TEXT
]
);

$this->add_control(
'subhead',
[
'label' => esc_html__('Subhead', 'goldenrod'),
'type' => \Elementor\Controls_Manager::TEXTAREA
]
);

$this->add_control(
'image',
[
'label' => __( 'Image', 'goldenrod' ),
'type' => \Elementor\Controls_Manager::MEDIA,
'default' => [
'url' => '',
],
'dynamic' => [
'active' => true,
],
]
);

$this->end_controls_section();
}

Finally, the most creative part: rendering the content. Here we use good old HTML, injecting via echo the values we can get from the get_settings_for_display() function . Here is an example:


protected function render() {
$settings = $this->get_settings_for_display();
?>
<div class="gr-week-container">
<p class="gr-week-kicker"><?php echo $settings['kicker']; ?></p>
<div class="gr-week-content-container">
<div class="gr-week-text-container">
<h5 class="gr-week-headline"><?php echo $settings['headline']; ?></h5>
<p class="gr-week-subhead"><?php echo $settings['subhead']; ?></p>
</div>
<div class="gr-week-image-container">
<img class="gr-week-image" src="<?php echo $settings['image']['url']; ?>" alt="<?php echo $settings['headline']; ?>" />
</div>
</div>
</div>

<?php
}

This process must be done for each widget we are going to create.

Putting the pieces together

We have created our widgets, but we don’t see them in Elementor! This is because we need to register the widgets with Elementor within our entry point goldenrod.php .

To do this, we use the register_widgets() function:


function register_widgets( $widgets_manager ) {
require_once(__DIR__ . '/base.php' );
require_once(__DIR__ . '/week.php' );
// import other widgets

$widgets_manager->register( new \Goldenrod_Week() );
// register other widgets
}

add_action( 'elementor/widgets/register', 'register_widgets' );

If we then need additional CSS or JavaScript code, we can do this by enqueuing styles and scripts, again in our entry point:

function register_goldenrod_assets() {
wp_enqueue_style( 'goldenrod', plugins_url('style.css', __FILE__));
wp_enqueue_script( 'goldenrod', plugins_url('scripts.js', __FILE__));
}

add_action( 'wp_enqueue_scripts', 'register_goldenrod_assets');

At this point our Elementor widget plugin should be ready to use. By dragging it into an Elementor page, we should be able to see it in action and play with the variables:

Wrapping up

Building sanmarinoexpo.com has been a curious experience, even after having used WordPress many times, and a way to learn more about how WordPress and Elementor work. This first collaboration of ours with the public sector will continue, and we will tell you about more things we have in store in the future.

We are letvar

We are a team of creatives focused on crafting apps for any device. We are based in San Marino, and we have more than a decade of experience in building great apps. Curious to learn more about us? Visit letvar.io.

The author of this article is Nicola Giancecchi, co-founder, CEO and Apple Platforms Engineer @ letvar

Feedbacks are welcome! hello@letvar.io

--

--

letvar

We are a team of creatives and focused on crafting apps for any device.