Controls
Controls are the inputs a user sees when they select your element. Element Studio's first
two tabs build them: Content produces the controls on the element's Content tab in the
builder, Design produces the ones on its Design tab. Both work identically, and both
compile to the same shape of PHP.
The split is a convention, not a rule enforced by code — put what the element says under
Content and what it looks like under Design, the way Oxygen's own elements do.
Sections and controls

Nothing can live at the top level of a tab except a section, so every element starts with
Add Control Section. A section is the collapsible group the user opens; Add Control
inside it adds the actual inputs.
Sections nest, and controls can nest inside controls that accept children (a repeater's
fields, for instance). In the generated PHP a section and a control are the same structure,
built by the same helper:
static function contentControls()
{
return [c(
"shortcode", // slug
"Shortcode", // label
[c( // children
"full_shortcode",
"Full Shortcode",
[],
['type' => 'text', 'layout' => 'vertical', 'placeholder' => '[myshortcode number="10"]', 'textOptions' => ['multiline' => true]],
false, // enable breakpoints
false, // enable hover
[], // keywords
)],
['type' => 'section', 'layout' => 'vertical'],
false,
false,
[],
)];
}
That's Oxygen's Shortcode element, unedited. A section is just a control whose type is
section.
The c() helper takes its arguments in this order:
c($slug, $label, $children, $options, $enableMediaQueries, $enableHover, $keywords = [], $dynamic = null)
You never type this by hand — the studio writes it — but knowing the shape makes the PHP
readable when you open an existing element to see how it works.
The slug is the property path
A control's slug is not cosmetic. It is the key its value is stored under, and the
path you use to read that value everywhere else:
content.shortcode.full_shortcode
└─────┘ └───────┘ └────────────┘
tab section control
That path is what you reference in HTML, in CSS, in
property paths, and what an agent sends over MCP. Renaming a slug
after people have used the element orphans their saved values, so choose carefully the
first time.
Choosing a control type
Every control has a type, picked from a dropdown in its options. The type decides both
the input the user gets and the shape of the value your template receives.
The common ones:
| Type | Input | Value |
|---|---|---|
text |
Single or multi-line text field | String |
richtext |
WYSIWYG editor | HTML string |
number |
Number field | Number |
unit |
Number plus a unit picker | String like 24px |
dropdown |
Select | The chosen item's value |
button_bar |
Segmented buttons | The chosen item's value |
multiselect |
Multi-select | Array of values |
toggle |
On/off switch | Boolean |
color |
Colour picker | Colour string |
wpmedia |
WordPress media library picker | Image object |
video |
Video picker | Video object |
icon |
Icon picker | Icon object |
link |
URL plus link options | Link object |
post_chooser |
Post picker | Post ID |
wp_query |
The full query builder | Query args |
repeater · inline_repeater |
Repeating group of child controls | Array of items |
code |
Code editor | String |
slider |
Range slider | Number |
date_picker · time_picker |
Date / time picker | String |
component_chooser |
Component picker | Component ID |
There are more — roughly fifty in total, including the composite design controls
(typography, shadow, gradient, border_complex, border_radius, spacing_complex,
focus_point) and builder-specific ones (breakpoint_dropdown, media_size_dropdown,
alert_box, interactions). Four types exist internally but are deliberately kept out of
the studio's dropdown: class, font_family, global_colors and message.
Each type brings its own options panel. A text control offers Multiline, Text Format,
Autofocus and Placeholder; a dropdown offers its list of items; a repeater offers a
title template and button label. Change the type and the options change with it.
Section layout
A section's layout decides how it opens:
accordion— expands in place in the properties panel. The default, and right for
anything a user touches often.popout— opens in a floating panel beside the properties panel. Good for dense groups.modal— opens a full dialog. For the rare section that needs the room.
Individual controls have a separate layout of inline (label and input on one line) or
vertical (label above the input). Long values — code, multiline text, URLs — want
vertical.
Breakpoints
Make this control's value available in all breakpoints turns one control into a
per-breakpoint control. The user gets a separate value for desktop, tablet and phone, and
the builder shows the usual breakpoint indicator on the control.
This is the enableMediaQueries argument — the fifth one in c(). Switch it on for
anything a responsive design needs to vary: font sizes, spacing, column counts, alignment.
Leave it off for content, which shouldn't change between screen sizes.
When it's on, the value is stored per breakpoint, so your template reads it through the
%%BREAKPOINT%% placeholder rather than directly. See CSS for how that plays
out in practice.
Hover states
Enable Hover (the enableHover argument) gives the control a hover variant, so the
user can set one value for the resting state and another for :hover. It belongs on design
controls — colours, borders, shadows, transforms — and nowhere near content controls.
Oxygen generates the :hover rule for you; your CSS template refers to the property as
normal.
Dynamic data
Enable dynamic property lets the user bind the control to a dynamic data field instead
of typing a fixed value — a post title, a custom field, an image from the media library.
Switching it on adds a dynamic entry to the control that names what the control
accepts:
['accepts' => 'string']
The value must be one of Oxygen's dynamic data return types — string, url,
image_url, gallery, video, query, google_map, repeater — and it acts as a
filter: the dynamic data picker only offers fields whose own return type matches. A control
that accepts image_url will not offer the user a post title.
Match the type to the control. A text control accepts string or url; a wpmedia
control accepts image_url. Getting this wrong doesn't break anything visibly — it just
means the picker shows the wrong fields, or none. The Dynamic Data
API covers the other side of this, registering fields of your
own.
Variables
Enable variables lets the control accept a
variable instead of a literal value, so a colour or
spacing control can be bound to a design token rather than a fixed value. Switch it on for
any design control whose value should follow the site's design system.
Showing a control conditionally
Only Show Control When Condition Is Met hides a control until another control in the
same element has a particular value — the standard way to keep an element's panel short
when half its options only apply in one mode.
Conditions combine as you'd expect: entries within one group are OR'd, and separate groups
are AND'd. Add new OR condition adds an alternative that also satisfies the same group.
Hide Control From Specific Elements does the reverse, and matters for controls that come
from a shared preset section: the preset defines the control once,
and an element that doesn't want it names it here rather than forking the preset.
Keywords
Keywords stores a list of extra terms on the control — the seventh argument to c(),
and an optional array in the control's data. None of Oxygen's own elements set it, and it
has no effect on how the control renders. Leave it empty unless you have your own reason to
tag controls.
Reading it back
The clearest way to understand any of this is to open an element that already does what you
want. Element Studio's Open element list includes every element Oxygen ships, and the
generated PHP for each of them sits in wp-content/plugins/oxygen-elements/elements/. The
Image element is a good one for conditions (its media picker only appears when the source
is set to the media library); Shortcode is a good one for the smallest possible control
tree.