CSS
Element Studio gives an element two stylesheets, on two tabs:
- CSS is a Twig template. It runs against the user's control values, so it's where
every style the user can change is generated. - Default CSS is plain CSS. It runs once, applies to every instance, and holds the
structural styles that make the element work regardless of what anyone sets.
Most elements need both.
The CSS tab

The template is saved as css.twig and starts life with a single empty rule:
.bde-themeless-template-content-area-256146-124 { }
.bde-themeless-template-content-area-256146-124 is replaced at render time with a selector matching the one element instance
being styled. It is the only correct way to target your element — writing a bare class
selector would style every instance on the page, including ones the user styled
differently.
Inside the braces, control values interpolate as Twig:
.bde-themeless-template-content-area-256146-124 {
background-color: {{ design.container.background }};
gap: {{ design.layout.gap.style }};
}
You can target descendants by extending the selector, which is how Breakdance's own loop
elements style their items:
.bde-themeless-template-content-area-256146-124 > .bde-loop > .bde-loop-item {
background-color: {{ design.post.background }};
}
Breakpoints are handled for you
Do not write media queries. Breakdance renders the CSS template once per breakpoint and
wraps each pass in that breakpoint's media query itself. On each pass, control values are
already flattened to that breakpoint's value — so {{ design.container.background }} means
"the background at this breakpoint", with inheritance from wider breakpoints resolved.
Use the generated breakpoint passes for responsive controls rather than adding a second set of media queries around their output.
A few extra variables are available if you need to vary output by breakpoint rather than by
value:
| Variable | What it holds |
|---|---|
breakpoint |
The current breakpoint's id |
isBaseBreakpoint |
true on the base (desktop) pass only |
builtinBreakpoints |
The built-in breakpoint definitions |
globalSettings |
The site's global settings |
childElementCount |
How many children the instance has |
isBaseBreakpoint is the useful one: wrap anything that should be emitted exactly once —
a @keyframes block, a CSS custom property definition — so it doesn't get repeated inside
every media query.
{% if isBaseBreakpoint %}
@keyframes my-element-fade { from { opacity: 0 } to { opacity: 1 } }
{% endif %}
Empty values
A control the user hasn't set interpolates as an empty string, which leaves you with
background-color: ; — an invalid declaration.
Guard properties that aren't always set:
{% if design.container.background %}
.bde-themeless-template-content-area-256146-124 { background-color: {{ design.container.background }}; }
{% endif %}
Macros
Breakdance's own CSS templates lean heavily on macros for the composite design controls,
because a border_complex or spacing_complex value is a structure, not a string:
.bde-themeless-template-content-area-256146-124 {
{{ macros.borders(design.container.borders) }}
{{ macros.spacing_padding_all(design.container.padding) }}
}
Each of those emits several declarations from one control value. If you add a composite
design control to your element, open a first-party element that already uses the same
control type and copy how it renders — that is far more reliable than reconstructing the
value's shape by hand. Macros covers writing your own.
Default CSS
The Default CSS tab is plain CSS with no Twig and no .bde-themeless-template-content-area-256146-124. It's emitted once
for the element type rather than per instance, so it targets the element's class name —
shown at the top of the tab and set on the Settings tab:
.my-icon {
display: inline-flex;
--my-icon-size: 1em;
justify-content: center;
align-items: center;
width: var(--my-icon-size);
height: var(--my-icon-size);
}
.my-icon > svg {
width: 100%;
height: 100%;
object-fit: contain;
}
This example supplies structural layout, default custom-property values, and styles for internal markup. A control can override a default through generated CSS.
New elements get a generated class name like autogenerated-ee-yourelement — the
lowercased element name, prefixed with initials taken from its namespace (ee for EssentialElements). You can replace it with something readable
on the Settings tab, but do it early: changing the class name later leaves your old CSS
pointing at a class nothing has any more, and the studio warns you about exactly that.
Which stylesheet gets a rule
| Put it in | When |
|---|---|
| Default CSS | The element needs it to work at all — display mode, positioning context, internal structure, custom-property defaults |
| CSS | A control decides it |
The order matters as well as the intent: default CSS loads before per-instance CSS, so per-instance rules can override base styles according to the normal CSS cascade and selector specificity. That's what lets
you set --my-icon-size: 1em as a default and have a size control override it cleanly.