Element API Primer

This article is a step-by-step guide instructing you how to get started building your own Elements.

  1. Getting Started
  2. Definition
  3. Values
  4. Control Navigation
  5. Controls
  6. Generated CSS
  7. Upgrading to a Builder callback
  8. Partials
  9. Custom Element Icon
  10. Standard / Advanced Controls
  11. Common Abstractions
  12. Summary

Getting Started

The Element API allows developers to extend Pro and Cornerstone through the creation of custom elements. This article will guide you through the process of creating an element, while also breaking down the different parts of an element.

To follow along, you may want to download the code used in this article.

Download Sample Element Plugin

This is setup as a plugin, but could just as easily be made into part of a child theme by requiring the my-element.php file in functions.php.

You can enable the application dev tools with this constant:

define( 'CS_APP_DEV_TOOLS', true );

You should now see a bug icon at the bottom of the Bar while using the builders. At the time of this writing, its only feature is to display the values of your currently inspected element.

An element is created by defining values, connecting them to controls to allow user customization, then displaying them on the site using a render function. All of this in done through the element's definition file. Let's begin creating an element.

Definition

The following code can be placed inside functions.php of a child theme, or in your custom plugin (like the sample element download).

function my_element_register() { require_once( './my-elelemt.php' ); } add_action( 'cs_register_elements', 'my_element_register' )

And here's our first version of my-element.php.

// Create values $values = cs_compose_values( [ 'text_content' => cs_value( 'I am a custom element', 'markup', true ), 'custom_attribute' => cs_value( false, 'attr', false ), 'custom_attribute_content' => cs_value( '', 'attr', false ), 'content_margin' => cs_value( '', 'style', false ), 'background_color' => cs_value( 'transparent', 'style', false ), 'background_color_hover' => cs_value( 'transparent', 'style', false ), ], 'omega', 'omega:toggle-hash' ); // Register "My Element" cs_register_element( 'my-custom-element', array( // Define a localized title of your element and how it is labeled in the Element Library 'title' => __( 'My Element', 'your-text-domain' ), // Define some values that we want to let users customize for this element 'values' => $values, 'builder' => 'my_element_builder', // Connect a function used to render this element 'render' => 'my_element_render', ) ); function my_element_builder() { return cs_compose_controls([ // Define the control groups that will appear in the inspector navigation bar 'control_nav' => [ 'my-element' => __( 'My Element', 'your-text-domain' ), 'my-element:setup' => __( 'Setup', 'your-text-domain' ), 'my-element:design' => __( 'Design', 'your-text-domain' ), ], // Define the controls that connect to our values. 'controls' => [ // Setup [ 'type' => 'group', 'label' => __( 'Setup', 'your-text-domain' ), 'group' => 'my-element:setup', 'controls' => [ // Setup: Content [ 'key' => 'text_content', 'type' => 'text-editor', 'label' => __( 'Content', 'your-text-domain' ), ], // Setup: Custom Attribute [ 'key' => 'custom_attribute', 'type' => 'choose', 'label' => __( 'Custom Attribute', 'your-text-domain' ), 'options' => [ 'choices' => [ [ 'value' => false, 'label' => __( 'Off', '__x__' ) ], [ 'value' => true, 'label' => __( 'On', '__x__' ) ], ], ] ], // Setup: Attribute Content [ 'key' => 'custom_attribute_content', 'type' => 'text', 'label' => __( 'Attribute Content', 'your-text-domain' ), 'condition' => array( 'custom_attribute' => true ), 'options' => array( 'monospace' => true ) ], [ 'keys' => array( 'color' => 'background_color', 'alt' => 'background_color_hover', ), 'type' => 'color', 'label' => __( 'Background Color', '__x__' ), 'options' => array( 'label' => __( 'Base', '__x__' ), 'alt_label' => __( 'Interaction', '__x__' ), ) ] ] ], // Content Margin [ 'key' => 'content_margin', 'type' => 'margin', 'label' => __( 'Content Margin', 'your-text-domain' ), 'group' => 'my-element:design', ] ] ], [ 'control_nav' => [ 'my-test' => __( 'My Test', 'your-text-domain' ), 'my-test:setup' => __( 'My Test', 'your-text-domain' ), ], 'controls' => [ // Setup [ 'type' => 'group', 'label' => __( 'Setup', 'your-text-domain' ), 'group' => 'my-test:setup', 'controls' => [ // Setup: Content [ 'key' => 'text_content', 'type' => 'text-editor', 'label' => __( 'Content', 'your-text-domain' ), ], ], ], ] ]); } function my_element_render( $data ) { $atts = cs_atts( array( 'class' => $data['classes'], 'data-my-attribute' => $data['custom_attribute'], ) ); return "<div $atts>{$data['text_content']}</div>"; }

This is what we call an Element Definition. You can browse the native element definitions in cornerstone/includes/elements/definitions. Keep in mind many of them use more advanced concepts than the file above. This primer will start with an element in the simplest form, then add several layers to make it more powerful.

Try it out! You'll have a working element. After adding it in the builder and inspecting the element you can try changing the content and see it update in the live preview. I know, plenty going on here. Let's start breaking down what this code means and how it works.

Values

Working from the top down, the title is pretty self explanatory so let's jump right into values.

// Define some values that we want to let users customize for this element 'values' => array( 'text_content' => cs_value( 'I am a custom element', 'markup', true ) ),

We are declaring a single value with the name text_content That key is what we will use anytime we want to refer to this value in other parts of our code. Here is the function signature for cs_value

function cs_value( $default = null, $designation = 'all', $protected = false )

The first argument ($default) defines the initial value. You probably already realized that when you noticed your text was pre-populated with "I am a custom element" when you added it the first time. Skipping briefly to the third argument ($protected), we can use this to flag this key as "Content" for when element presets are saved and applied. When applying

Using the correct value of the second argument ($designation) is important for managing the live preview performance of your element. It tells the rendering system the kind of value we are working with. Here are the main designation types:

  • markup - When this value changes it makes a HTTP request to the server with the updated attributes and fetches new markup for the builder preview. This is the most expensive in terms of performance, but most versatile as it invokes your custom PHP code.
  • attr - When this value changes it updates the value in the DOM immediately.
  • style - We'll cover this later on, but our builders allow you to provide a style template to output generated CSS. When a value with this designation changes, it causes the generated CSS to recalculate.
  • all - When this value changes it cause the element to fetch new markup from the server and renders the generated CSS again.

The key take away is that the builders are smart enough to use the least amount of resources when rendering elements. Use style (covered later) and attr when possible to avoid unnecessary requests to the server. Let's try adding an attr designated value. Update your my-element.php to the following:

// Register "My Element" cs_register_element( 'my-custom-element', array( // Define a localized title of your element and how it is labeled in the Element Library 'title' => __( 'My Element', 'your-text-domain' ), // Define some values that we want to let users customize for this element 'values' => array( 'text_content' => cs_value( 'I am a custom element', 'markup', true ), 'custom_attribute' => cs_value( '', 'attr', false ) ), // Define the control navigation used to organize controls in the Inspector 'control_nav' => array( 'my-element' => __( 'My Element', 'your-text-domain' ), 'my-element:setup' => __( 'Setup', 'your-text-domain' ), 'my-element:design' => __( 'Design', 'your-text-domain' ), ), // Define the controls that connect to our values. 'controls' => array( array( 'key' => 'text_content', 'type' => 'text-editor', 'group' => 'my-element:setup', 'label' => __( 'Content', 'your-text-domain' ), ), array( 'key' => 'custom_attribute', 'type' => 'text', 'group' => 'my-element:design', 'label' => __( 'Custom Attribute', 'your-text-domain' ), ) ), // Connect a function used to render this element 'render' => 'my_element_render', ) ); function my_element_render( $data ) { extract( $data ); $atts = cs_atts( array( 'class' => 'my-element', 'data-my-attribute' => $custom_attribute ) ); return "<div $atts>$text_content</div>"; }

Here are the changes we introduced:

  • Added a custom_attribute value with the attr designation
  • Added a new control and control nav. We'll explore these concepts deeper in the next section
  • Updated the render function to output data-my-attribute on our div with the value stored in custom_attribute

You'll also notice a new function: cs_atts. This is simply a helper method that returns a string of HTML attributes from an array and applies the correct output sanitization.

Using your browser's developer tools, inspect your element in the DOM. See how data-my-attribute is now on your element? Start typing in the text input we've connected to that value. Watch how it updates in realtime without any HTTP requests to the server!

Control Navigation

Before looking at controls themselves, let's start by understanding control_navand how it is used for organization. Here is what we have currently:

'control_nav' => array( 'my-element' => __( 'My Element', 'your-text-domain' ), 'my-element:setup' => __( 'Setup', 'your-text-domain' ), 'my-element:design' => __( 'Design', 'your-text-domain' ), ),

This is used to organize your controls into digestible sections. Using a colon delineates the subsections of a parent group. This means "My Element" will be a top level group with "Setup" and "Design" as navigation links inside it. Our Testimonial element is a good example of using more groups.

'control_nav' => array( 'testimonial' => array( 'title' => __( 'Testimonial', '__x__' ) ), 'testimonial:content' => array( 'title' => __( 'Content', '__x__' ) ), 'testimonial:setup' => array( 'title' => __( 'Setup', '__x__' ) ), 'testimonial:design' => array( 'title' => __( 'Design', '__x__' ) ), 'testimonial:text' => array( 'title' => __( 'Text', '__x__' ) ), 'testimonial_cite' => array( 'title' => __( 'Citation', '__x__' ) ), 'testimonial_cite:setup' => array( 'title' => __( 'Setup', '__x__' ) ), 'testimonial_cite:design' => array( 'title' => __( 'Design', '__x__' ) ), 'testimonial_cite:text' => array( 'title' => __( 'Text', '__x__' ) ), 'testimonial_graphic' => array( 'title' => __( 'Graphic', '__x__' ) ), 'testimonial_graphic:setup' => array( 'title' => __( 'Setup', '__x__' ) ), ),

If you try out the Testimonial element, you'll see how the code relates to the resulting navigation structure. (You might be wondering how "Customize" got in there. It comes from a "partial" which we'll learn about later.)

For smaller groups we use :setup and :design as sub navigation items by convention. We would recommend using this when appropriate as Cornerstone and Pro users are already familiar with it, but you're more than welcome to add whatever navigation items you want.

Remember that a navigation item will be hidden if it has no controls associated with it, or all of its controls are hidden (see Control Conditions).

Controls

If you're thinking about how you could integrate a shortcode from your plugin as a cornerstone element, you've probably already caught on to how powerful this is. But honestly, this element isn't all that interesting just yet. Now let's talk about how you can add more controls to give your users more customization power.

Control Types

There are obviously more control types than text editors. Let's pretend that our custom_attribute is something that users can toggle on or off rather. This means we can use a choose control. Update the type of your control to choose and add an options array with some choices that include On and Off.

array( 'key' => 'custom_attribute', 'type' => 'choose', 'group' => 'my-element:design', 'label' => __( 'Custom Attribute', 'your-text-domain' ), 'options' => array( 'choices' => array( array( 'value' => false, 'label' => __( 'Off', '__x__' ) ), array( 'value' => true, 'label' => __( 'On', '__x__' ) ), ) ) )

We also want to update our value to have a boolean default to match our new control

'custom_attribute' => cs_value( false, 'attr', false )

Now if you inspect the element you will see the choose control that allows toggling between on and off. You can use your browser's developer tools to observe it switch back and forth.

Control Groups

Compared to other elements you've seen before, you'll notice we're wasting a bunch of space by placing our two controls in separate boxes. By default, every top level control has a separate "box". This isn't very efficient when using the Inspector horizontally. Fix this by combining our two controls into a single group control. This is a special control type used for organizing controls.

// Define the controls that connect to our values. 'controls' => array( array( 'type' => 'group', 'label' => __( 'Setup', 'your-text-domain' ), 'group' => 'my-element:setup', 'controls' => array( array( 'key' => 'text_content', 'type' => 'text-editor', 'label' => __( 'Content', 'your-text-domain' ), ), array( 'key' => 'custom_attribute', 'type' => 'choose', 'label' => __( 'Custom Attribute', 'your-text-domain' ), 'options' => array( 'choices' => array( array( 'value' => false, 'label' => __( 'Off', '__x__' ) ), array( 'value' => true, 'label' => __( 'On', '__x__' ) ), ), ) ) ) ) ),

The nested controls don't need to specify which navigation item them belong to because that is the responsibility of the group. Not all control types (border or link for example) can be used inside of a group because they require more space. Use the control reference to identify which controls can be used this way.

Control Conditions

What if we want another control to appear if our Custom Attribute is turned on? Let's add a new control that employs a condition. First we need a value:

'custom_attribute_content' => cs_value( '', 'attr', false ),

Next we'll add a text control. We'll also present it as monospace. Place this immediately after the existing choose control

array( 'key' => 'custom_attribute_content', 'type' => 'text', 'label' => __( 'Custom Attribute Content', 'your-text-domain' ), 'options' => array( 'monospace' => true ) )

If you test this out, you'll notice toggling On/Off doesn't do anything. We need to add a condition to our control. Here's the updated code:

array( 'key' => 'custom_attribute_content', 'type' => 'text', 'label' => __( 'Attribute Content', 'your-text-domain' ), 'condition' => array( 'custom_attribute' => true ), 'options' => array( 'monospace' => true ) )

Now the content text input only shows when the choose control is turned on. We're using the simplest condition shorthand that says "Show this control if the value of custom_attribute is true". The API supports checking multiple conditions and offer several operation types. Learn more about this LINK

First Styling Controls

Ready for some styling? Let's add some values that we will connect to some styling controls. Add the following lines to your values array:

'background_color' => cs_value( 'transparent', 'style', false ), 'background_color_hover' => cs_value( 'transparent', 'style', false ), 'content_margin' => cs_value( '0em', 'style', false ),

Some controls allow you to connect multiple values. For example, a single color control can output two colors. Let's use this to add a background color. Add the following at the end of your group control's control list (after our choose for custom_attribute ).

array( 'keys' => array( 'color' => 'background_color', 'alt' => 'background_color_hover', ), 'type' => 'color', 'label' => __( 'Background Color', '__x__' ), 'options' => array( 'label' => __( 'Base', '__x__' ), 'alt_label' => __( 'Interaction', '__x__' ), ) )

Inspect your element and you'll see two color pickers side by side. Because we don't have generated CSS yet, now would be a good time to try out the dev tools. If you enabled them with the CS_APP_DEV_TOOLS constant you can click the "Bug" icon in the Bar. You'll see an overlay cover the preview area and show the values of your element. Adjust the color pickers and you can see the output changing.

Next we'll add a margin control that we will use to allow customizing spacing on our main element. It can only be used top level (not inside a group), so add this control immediately following the group control.

array( 'key' => 'content_margin', 'type' => 'margin', 'label' => __( 'Content Margin', 'your-text-domain' ), 'group' => 'my-element:design', )

Now we're back to two top level boxes!

Generated CSS

We have a unique "Generated CSS" system that takes a style template (a string) and uses it to create unique styles for each element. It is optimized so that when elements of the same type have the same values, a combined CSS selector is used with one property declaration. The first thing we need to do is update our render function to output the element's unique classname. This is automatically available for you as it is passed through $data under the key mod_id. Here is the updated render function:

function my_element_render( $data ) { extract( $data ); $atts = cs_atts( array( 'class' => cs_attr_class( $mod_id, 'my-element' ), 'data-my-attribute' => $custom_attribute ) ); return "<div $atts>$text_content</div>"; }

The cs_attr_class function accepts unlimited string arguments and combines them into one class. Now we have a unique class attached to our element that we can use to apply styles. On the front end the class will be a unique identified based on the current context (page, header, etc.) and in the builders it will be a random string.

To begin outputting styles, we need to add a style template to our definition. In the array passed to cs_register_element add the following. Putting it above the render callback is a good place.

'style' => 'my_element_style'

Here is the function itself which can be added above your my_element_render function.

<?php function my_element_style() { ob_start(); ?> .$_el { background-color: $background_color; margin: $content_margin; } .$_el:hover { background-color: $background_color_hover; } <?php return ob_get_clean(); }

The function needs to return a string, so we use some output buffering for the sake of readability. You could also place these style in a separate file. This is how the sample element download is setup. For example:

function my_element_style() { ob_start(); include 'path/to/my-element-css.php'; return ob_get_clean(); }

Now that our template is loading, we can test the element. As you adjust your controls the CSS will update in the live preview. You can learn more about how to write these templates in the Style Template Syntax section.

Upgrading to a Builder callback

Before we advance to some of the most powerful API features like Partials, we'll need to make a slight adjustment to our definition file. Here's an updated my-element.php including all the changes we've made so far as well as the builder callback.

<?php // Register "My Element" cs_register_element( 'my-custom-element', array( // Define a localized title of your element and how it is labeled in the Element Library 'title' => __( 'My Element', 'your-text-domain' ), // Define some values that we want to let users customize for this element 'values' => array( 'text_content' => cs_value( 'I am a custom element', 'markup', true ), 'custom_attribute' => cs_value( false, 'attr', false ), 'custom_attribute_content' => cs_value( '', 'attr', false ), 'content_margin' => cs_value( '', 'style', false ), 'background_color' => cs_value( 'transparent', 'style', false ), 'background_color_hover' => cs_value( 'transparent', 'style', false ), ), // 'builder' => 'my_element_builder_setup', // Connect the style template callback 'style' => 'my_element_style', // Connect a function used to render this element 'render' => 'my_element_render', ) ); // Builder Setup function my_element_builder_setup() { return array( // Define the control groups that will appear in the inspector navigation bar 'control_nav' => array( 'my-element' => __( 'My Element', 'your-text-domain' ), 'my-element:setup' => __( 'Setup', 'your-text-domain' ), 'my-element:design' => __( 'Design', 'your-text-domain' ), ), // Define the controls that connect to our values. 'controls' => array( // Setup array( 'type' => 'group', 'label' => __( 'Setup', 'your-text-domain' ), 'group' => 'my-element:setup', 'controls' => array( // Setup: Content array( 'key' => 'text_content', 'type' => 'text-editor', 'label' => __( 'Content', 'your-text-domain' ), ), // Setup: Custom Attribute array( 'key' => 'custom_attribute', 'type' => 'choose', 'label' => __( 'Custom Attribute', 'your-text-domain' ), 'options' => array( 'choices' => array( array( 'value' => false, 'label' => __( 'Off', '__x__' ) ), array( 'value' => true, 'label' => __( 'On', '__x__' ) ), ), ) ), // Setup: Attribute Content array( 'key' => 'custom_attribute_content', 'type' => 'text', 'label' => __( 'Attribute Content', 'your-text-domain' ), 'condition' => array( 'custom_attribute' => true ), 'options' => array( 'monospace' => true ) ), array( 'keys' => array( 'color' => 'background_color', 'alt' => 'background_color_hover', ), 'type' => 'color', 'label' => __( 'Background Color', '__x__' ), 'options' => array( 'label' => __( 'Base', '__x__' ), 'alt_label' => __( 'Interaction', '__x__' ), ) ) ) ), // Content Margin array( 'key' => 'content_margin', 'type' => 'margin', 'label' => __( 'Content Margin', 'your-text-domain' ), 'group' => 'my-element:design', ) ) ); } // Style Template function my_element_style() { ob_start(); ?> .$_el { background-color: $background_color; margin: $content_margin; } .$_el:hover { background-color: $background_color_hover; } <?php return ob_get_clean(); } // Render function my_element_render( $data ) { extract( $data ); $atts = cs_atts( array( 'class' => cs_attr_class( $mod_id, 'my-element' ), 'data-my-attribute' => $custom_attribute ) ); return "<div $atts>$text_content</div>"; }

What we've done is taken our code definition and split it into parts. This line tells the builder to call the my_element_builder_callback function.

'builder' => 'my_element_builder_callback'

The array returned from your callback will update the element definition with the new values. The reason we do this is because the builder callback doesn't run on the front end. For performance, we should avoid running any code that isn't essential to the front end. The cumulative native library represents thousands of controls which would mean considerable memory usage if they were all defined on every front end page load.

This adjustment also opens us up to builder only API features like some abstraction techniques and of course Partials.

Partials

This is where you really start to see how the API can be leveraged. What if you want to start with our native button element and add a few additional controls? Or what if you want to reproduce one of our modal elements but supply your own content? Partials are the solution.

Adding the Customize Partial

For our first example, we will learn how to add those "Customize" controls. We called this the Omega partial, because it goes at the end of nearly ever native element. Remember that elements require a combination of both values and controls. We'll start by defining the valueswe need. Update your definitions values to look like this:

'values' => cs_compose_values( array( 'text_content' => cs_value( 'I am a custom element', 'markup', true ), 'custom_attribute' => cs_value( false, 'attr', false ), 'custom_attribute_content' => cs_value( '', 'attr', false ), 'content_margin' => cs_value( '', 'style', false ), 'background_color' => cs_value( 'transparent', 'style', false ), 'background_color_hover' => cs_value( 'transparent', 'style', false ), ), 'omega' ),

The cs_compose_values function takes an unlimited number of arguments. If you pass an array, those values will pass through. If you pass a string, it will locate predefined values for that key. If you take a look in cornerstone/includes/elements/values.php you can see all the predefined values available. To summarize foromega, we are defining the values below that will also have controls associated with them.

  • id - Text input to specify the element ID
  • class - Text input to add custom classes
  • css - Code editor for Element CSS
  • hide_bp - Choose control to hide based on current breakpoint. class is automatically updated behind the scenes based on this value.

We'll also want to compose our controls in a similar fashion. Update your builder callback to look like this (actual controls omitted for brevity.)

function my_element_builder_setup() { return cs_compose_controls( array( 'control_nav' => array( /* ... */), 'controls' => array( /* ... */) ), cs_partial_controls( 'omega' ) ); }

The cs_partial_controls function returns an array containing controls and control_nav. Then cs_compose_controls combines them into a single output. To make this work on the front end we need to update our render function to use these new values. We only need to add the ID attribute and update the class attribute. Element CSS and breakpoint visibility are automatically handled behind the scenes.

function my_element_render( $data ) { extract( $data ); $atts = cs_atts( array( 'id' => $id, 'class' => cs_attr_class( $mod_id, 'my-element', $class ), 'data-my-attribute' => $custom_attribute ) ); return "<div $atts>$text_content</div>"; }

Background

Another common partial you may want to use is the advanced background. As with Omega, we need to update our values, controls, and render function. The code examples in this section have areas shortened for brevity. Don't remove any existing values or controls.

First we'll update our values. Simply add bg to the list.

'values' => cs_compose_values( array( /* ... */ ), 'bg', 'omega' ),

Here's what we need to add the partial controls. Notice I left a commented out condition line. Some partials like bg support conditions to hide/show all their controls. We use this in native elements to allow users to turn advanced backgrounds on or off.

function my_element_builder_setup() { return cs_compose_controls( array( 'control_nav' => array( /* ... */), 'controls' => array( /* ... */) ), cs_partial_controls( 'bg', array( 'group' => 'my-element:design', // 'condition' => array( 'custom_attribute' => true ), ) ), cs_partial_controls( 'omega' ) ); }

Finally, let's get this outputting in our render function.

<?php function my_element_render( $data ) { extract( $data ); $atts = cs_atts( array( 'id' => $id, 'class' => cs_attr_class( $mod_id, 'my-element', $class ), 'data-my-attribute' => $custom_attribute ) ); ob_start(); ?> <div <?php echo $atts; ?>> <?php echo cs_get_partial_view( 'bg', cs_extract( $data, array( 'bg' => '' ) ) ); ?> <div><?php echo $text_content; ?></div> </div>" <?php return ob_get_clean(); }

Two new functions have just been introduced. cs_get_partial_view is used to get markup for partials that have their own view. The first parameter is the partial view name, and the second is the data you want to pass into that partial. This is very much like our element's render function and partial views expect data that corresponds with the partial values.

To populate the correct data, we use cs_extract to filter our element data into exactly what this partial needs. For the background, we're requesting that we only receive values that start with a bg prefix.

Did you try it out? Uh oh, you'll have noticed that the background partial bleeds out into the parent element. This is because bg outputs a div that has position: absolute. To correct this, we need to make our element use relative positioning. We'll make that adjustment in the style template:

& { position: relative; background-color: $background_color; margin: $content_margin; }

That's it for the walkthrough! The remainder of this article doesn't use the same code examples we've been working with. We'll continue to scratch the surface of other important Element API concepts, and there's more to learn by combing through the API Reference.

Custom Element Icon

You can add a custom element icon by providing an inline SVG in your definition under the icon key. For example:

cs_register_element( 'my-custom-element', array( // Define a localized title of your element and how it is labeled in the Element Library 'title' => __( 'My Element', 'your-text-domain' ), 'icon' => '<svg>/* your SVG code */</svg>' ...

Standard / Advanced Controls

Users can toggle Advanced Mode from their preferences. Everything we've done so far is what is shown when this is turned on. When Advanced Mode is turned off, we can provide a standard mapping that is more simple and doesn't have multiple control navigations. There are three sections we can map controls to:

  • controls_std_content - Content controls like text inputs, links, images, etc.
  • controls_std_design_setup - Things like base font size, widths, text alignment, etc.
  • controls_std_design_colors - Any and all color controls.

Here's a code example of adding these keys to your builder callback:

function my_element_builder_setup() { return cs_compose_controls( array( 'control_nav' => array( /* ... */), 'controls' => array( /* ... */), 'controls_std_content' => array( /* ... */), 'controls_std_design_setup' => array( /* ... */), 'controls_std_design_colors' => array( /* ... */), ), cs_partial_controls( 'bg', array( 'group' => 'my-element:design' ) ), cs_partial_controls( 'omega' ) ); }

cs_compose_controls always adds controls to the list in the order that they are passed in. You may run into a situation where you want more advanced controls to appear after the background partial. To accomplish this, just add another array after the partial controls.

function my_element_builder_setup() { return cs_compose_controls( array( 'control_nav' => array( /* ... */), 'controls' => array( /* ... */), 'controls_std_content' => array( /* ... */), 'controls_std_design_setup' => array( /* ... */), 'controls_std_design_colors' => array( /* ... */), ), cs_partial_controls( 'bg', array( 'group' => 'my-element:design' ) ), array( 'controls' => array( /* ... */), ), cs_partial_controls( 'omega' ) ); }

For a complex example of this, see the native Card element definition (cornerstone/includes/elements/definitions/card.php).

Common Abstractions

Frequently used data

Remember how we created a choose control with On/Off? This is something very common in our native elements, so we used an abstraction to help reduce repeated code. You can replace the choices with the following cs_remember call:

'options' => array( 'choices' => cs_recall( 'options_choices_off_on_bool' ) )

You can find a handful of other common things we abstracted in the cornerstone/includes/elements/registry-setup.php file. You can also make your own by using the cs_remember function. This is handy if you have multiple elements that need similar options.

// Before definitions are registered cs_remember( 'my_options', array( /* anything */ ) ); // Anywhere inside your builder function. $my_options = cs_remember( 'my_options' );

These can only be used inside the builder functions to avoid unnecessary memory usage on the front end.

Control Shorthand

Styling controls like margin, padding, border, etc. are prolific in our native elements, so to reduce boilerplate and enforce a convention we offer the cs_control function. To demonstrate this, the margin control we created in the styling section above can be replaced with this code:

cs_control( 'margin', 'content', array( 'label_prefix' => __( 'Content', 'your-text-domain' ), 'group' => 'my-element:design', ) )
  • First argument is the control type.
  • Second argument is the key prefix. This means our control key will result in: content_margin.
  • The third element is any other control parameters we need.
  • We can now use label_prefix. The result will be "Content Margin".

Using cs_control is completely optional - but we rely on it heavily so you should be aware of what it does as you explore the native element code.

Summary

We've delved into creating custom elements for the Cornerstone in the X and Content Builder in the Pro theme. We've learned how every element has a definition which is primarily composed of values, controls, and a render function. To go more in-depth be sure to look over the API Reference.

See something inaccurate? Let us know