| name | bricks-custom-controls |
| description | Use when writing custom-element controls or debugging control behavior: "what control type for a color?", "conditional control visibility", "dynamic data on a custom control". Covers the ~40 control types, the `required` syntax, CSS-binding via `css` key, dynamic-data support, and default-value gotchas. |
Requires: Bricks 2.4+ with the Abilities API enabled
Update check
Run first when filesystem tools are available:
_BS_UPDATE_CHECK=""
for _CAND in "$HOME/.bricks/skills/bricks-skills/scripts/bricks-skills-update-check" "$PWD/scripts/bricks-skills-update-check" "$HOME/.claude/skills/bricks-skills/scripts/bricks-skills-update-check" "$HOME/.codex/skills/bricks-skills/scripts/bricks-skills-update-check"; do
[ -f "$_CAND" ] && _BS_UPDATE_CHECK="$_CAND" && break
done
[ -n "$_BS_UPDATE_CHECK" ] && sh "$_BS_UPDATE_CHECK" || true
If it prints BRICKS_SKILLS_UPDATE_AVAILABLE <old> <new> <tag>, load bricks-skills-update before continuing. If it prints BRICKS_SKILLS_JUST_UPDATED <old> <new>, mention the new version and continue.
Bricks: custom controls
Controls are the panel inputs users see when they select an element. Each control maps to a type from Bricks' registry. Use this as the reference: what types exist, how to bind them to CSS automatically, how to conditionally show them, and how to enable dynamic data.
Pair with custom-elements for the full element-authoring flow.
If a bricks/* ability is not available as a direct tool: first check whether it is outside the fast path and call it through mcp-adapter-execute-ability with ability_name: "bricks/<name>". If the dispatcher also rejects it, call bricks-list-ability-status to check whether a site admin disabled it under Bricks > AI.
Getting the canonical type list
The list below is the durable set, but exact shape and option list per type depend on the Bricks version. Canonical sources, in order of authority:
- Runtime MCP schema: call
mcp-adapter-execute-ability with ability_name: "bricks/get-element-schema" and parameters: { "elementName": "<name>" }. It returns the full control tree for one element and is dispatcher-only on the default Bricks MCP server.
includes/html-to-bricks/control-index.generated.php: every CSS-bound control across the element library, generated by scripts/generate-control-index.mjs.
- Grep
/includes/elements/*.php for 'type' =>: source of truth, but noisy: you'll hit sub-control values ('type' => 'monthly' inside a select option) mixed with real control types.
The ~40 control types
As of Bricks 2.x (verified by running the grep in sections 1-3 above against /includes/elements/*.php):
Input controls
text: single-line string input
textarea: multi-line string
email: email-validated input
number: numeric input (min, max, step supported)
code: CodeMirror editor (common modes include css, javascript, text/html, htmlmixed, application/json, and application/x-httpd-php-open)
editor: TinyMCE rich text editor
Choice controls
checkbox: boolean toggle
select: dropdown (pass options array)
Style tokens
color: color picker, integrates with theme palette
background: background composite (color, gradient, image)
gradient: gradient picker
border: border composite (width, style, color, radius)
box-shadow: shadow picker (multiple shadows supported)
text-shadow: text shadow picker
typography: composite (font-family, size, line-height, weight, letter-spacing, style)
text-decoration: text decoration composite
spacing: spacing shorthand
dimensions: 4-side dimensions (top / right / bottom / left)
transform: CSS transform composite (translate / rotate / scale / skew)
separator: visual separator in the panel (not an input)
Alignment
text-align: left / center / right / justify
align-items: flex align-items
justify-content: flex justify-content
direction: flex direction / orientation
Media & assets
image: single image (media library)
image-gallery: multiple images
audio: audio URL / media picker
video: video URL / media picker
svg: SVG upload with sanitization
icon: Themify Icons / custom icon library
link: link picker (internal / external / dynamic)
Structural
repeater: array of items with sub-controls
info: static informational text (not an input)
apply: action button (triggers a save action)
Advanced
query: query-builder UI (returns query args)
query-list: dropdown of queries on the current page
filters: CSS filters composite
datepicker: date picker (Flatpickr)
comment: comment editor used by the Post Comments element
The list is in-flux per Bricks version. If you need a type not above, grep /includes/elements/*.php and /includes/theme-styles/controls/*.php for 'type' => to see live usage. Do not assume radio exists as a standalone control type unless the target schema shows it.
Control shape
$this->controls['myControl'] = [
'group' => 'style',
'label' => 'My control',
'type' => 'color',
'default' => '#ff0000',
'css' => [ ],
'required' => [ ],
'description' => 'Shown under the control as help text',
'info' => 'Shown above: for warnings/notes',
];
Keys beyond type are optional, but label + type are minimum. group references a key you defined in set_control_groups(): omit to put the control in the default content tab.
CSS binding: the magic
Bricks generates frontend CSS from control values automatically when you add a css entry:
'bgColor' => [
'label' => 'Background',
'type' => 'color',
'css' => [
[
'property' => 'background-color',
'selector' => '',
],
],
],
For non-root selectors:
'iconColor' => [
'label' => 'Icon color',
'type' => 'color',
'css' => [
[
'property' => 'color',
'selector' => '.icon-wrapper i',
],
],
],
You do not emit <style> in render(). Bricks collects every css entry across all controls, generates a stylesheet, and loads it (inline or file, see bricks-performance skill).
Composite controls auto-bind to multiple properties. typography writes font-family, font-size, line-height, etc.: one entry covers them all:
'cardTypography' => [
'type' => 'typography',
'css' => [
[
'property' => 'font',
'selector' => '',
],
],
],
The special 'property' => 'font' treats the typography composite as a full font shorthand.
Conditional visibility: required
Show a control only when another has a specific value:
'highlight' => [ 'type' => 'checkbox', 'label' => 'Highlight' ],
'highlightColor' => [
'type' => 'color',
'label' => 'Highlight color',
'required' => [ 'highlight', '=', true ],
],
Syntax: [ control_name, operator, value ].
Operators (confirmed from real usage across elements):
= / ==: equals
!=: not equals
===: strict equals
!==: strict not equals
Value can be:
- Scalar (
true, 'email', 42)
- Array (matches any:
[ 'email', 'tel', 'url' ])
Multiple conditions (AND):
'required' => [
[ 'type', '=', 'password' ],
[ 'passwordToggle', '=', true ],
],
Nested paths supported: dot-notation to access composite values:
'required' => [ 'icon.icon', '!=', '' ],
Example from accordion.php:143.
Dynamic data support: the flag
By default, most text-like controls accept dynamic data. To disable:
'internalId' => [
'type' => 'text',
'label' => 'Internal ID',
'hasDynamicData' => false,
],
From text.php:69. Useful for config fields that must remain literal (selectors, attribute names).
Rule: leave it default (omit the key) for content fields. Explicitly false for structural IDs / selectors that shouldn't be runtime-interpolated.
Defaults: the cascade
default value applies when a new element instance is added. For composite controls, default is an array:
'padding' => [
'type' => 'dimensions',
'default' => [ 'top' => '20px', 'right' => '20px', 'bottom' => '20px', 'left' => '20px' ],
],
Default values are not re-applied on version bumps. If you change a default, existing sites keep their old value. Plan accordingly.
Placeholder
Some controls support placeholder: shown when empty:
'heading' => [
'type' => 'text',
'placeholder' => 'Enter heading',
],
For composites, placeholder can also be an array (e.g., dimensions).
info and description
info: shows above the control, styled like a warning. Use for important rules ("Requires SSL").
description / desc: shows below, styled like help text. Use for "Learn more" links or clarifications.
content (for info type controls only): the text to display for a stand-alone info block.
Repeater controls
Array of sub-controls:
'features' => [
'type' => 'repeater',
'label' => 'Features',
'titleProperty' => 'name',
'placeholder' => 'Feature',
'fields' => [
'name' => [ 'type' => 'text', 'label' => 'Name' ],
'icon' => [ 'type' => 'icon', 'label' => 'Icon' ],
'color' => [ 'type' => 'color', 'label' => 'Color' ],
],
],
Access in render():
$features = $this->settings['features'] ?? [];
foreach ( $features as $feature ) {
echo '<div>' . esc_html( $feature['name'] ?? '' ) . '</div>';
}
Query controls
query: full query-builder UI (user picks post type, taxonomy, etc.).
query-list: dropdown that lists queries present on the current page; returns the target query's _id.
Use query-list for elements that target existing queries (filters, pagination elements pointing at a loop).
Link control
'myLink' => [
'type' => 'link',
'label' => 'Link',
],
Returns array:
[
'type' => 'internal',
'url' => '...',
'newTab' => true,
]
Use Bricks' render_attributes helper or manually construct <a href> + target.
Icon control
Picks from Themify Icons (default) or custom icon set. Returns:
[
'library' => 'themify',
'icon' => 'ti-star',
'custom' => '',
]
Emit:
$icon = $this->settings['myIcon'] ?? [];
if ( ! empty( $icon['icon'] ) ) {
echo '<i class="' . esc_attr( $icon['icon'] ) . '"></i>';
}
Code control
'customCss' => [
'type' => 'code',
'label' => 'Custom CSS',
'mode' => 'css',
],
Modes are CodeMirror modes, not Bricks execution modes. Current source uses values such as css, javascript, text/html, htmlmixed, application/json, and application/x-httpd-php-open (includes/elements/code.php:97, includes/elements/html.php:22, includes/settings/settings-page.php:531).
A custom code control is just an editor unless your render logic executes the stored value. If you execute stored PHP or JS, gate that execution explicitly and follow the bricks-custom-code skill. executeCode and signCode enable signing UI only for controls that opt into that contract; they do not make arbitrary custom controls safe by themselves (src/vue/components/main/panel/controls/ControlCode.vue:8, src/vue/components/main/panel/controls/ControlCode.vue:273).
Silent-failure debug order
-
Control doesn't appear in panel?
a. Parent group not declared in set_control_groups().
b. required condition never evaluates true.
-
CSS binding doesn't apply?
a. Selector in css entry doesn't match rendered HTML.
b. Control has no value (default not set, user hasn't interacted).
c. Another control with higher specificity overrides (composite wins over composite-leaf).
-
Value from control not reaching render()?
a. Control key mismatch: $this->controls['foo'] vs $this->settings['bar'].
b. Builder not saved after control edit.
-
Dynamic data picker missing on a text control?
a. hasDynamicData explicitly set to false somewhere in the control chain.
-
Repeater values not persisting?
a. Missing fields array.
b. Sub-control names conflict with parent control names (namespace collision).
Never do
- Emit inline
<style> from render() when css bindings can do the same. Bricks' CSS generator handles it: duplicating in PHP fights the system.
- Use
required with a non-existent control key. Silent truthy fallback leads to always-visible controls.
- Skip
hasDynamicData: false on structural ID / selector fields: users will type {post_title} and wonder why layout breaks.
- Execute values from a
code control without your own capability gate. The control type provides an editor; your render path decides whether the value is executable.
- Ship a control with no
label: it renders as a blank row in the panel.
Related skills
custom-elements: where controls live, full element authoring.
dynamic-data: how hasDynamicData fits into the tag pipeline.
custom-code: security model for places where Bricks actually executes stored code.