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.
You can enable the application dev tools with this constant:
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.
The following code can be placed inside
functions.php of a child theme, or in your custom plugin. It assumes we've created a
And here's our first version of
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.
Working from the top down, the
title is pretty self explanatory so let's jump right into
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
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:
Here are the changes we introduced:
custom_attributevalue with the
data-my-attributeon our div with the value stored in
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!
Before looking at controls themselves, let's start by understanding
control_navand how it is used for organization. Here is what we have currently:
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.
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
: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).
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.
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.
We also want to update our value to have a boolean default to match our new control
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.
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.
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 (
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.
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:
Next we'll add a text control. We'll also present it as monospace. Place this immediately after the existing
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:
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
true". The API supports checking multiple conditions and offer several operation types. Learn more about this [LINK]
Ready for some styling? Let's add some values that we will connect to some styling controls. Add the following lines to your
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
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
Now we're back to two top level boxes!
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:
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.
Here is the function itself which can be added above your
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:
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 of the API Reference.
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.
What we've done is taken our code definition and split it into parts. This line tells the builder to call the
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.
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.
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:
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 for
omega, 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.
classis 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.)
cs_partial_controls function returns an array containing
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.
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.
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.
Finally, let's get this outputting in our render function.
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
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:
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.
You can add a custom element icon by providing an inline SVG in your definition under the
icon key. For example:
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:
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.
For a complex example of this, see the native Card element definition (
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
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.
These can only be used inside the builder functions to avoid unnecessary memory usage on the front end.
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:
label_prefix. The result will be "Content Margin".
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.
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.