Skip to main content
Documentation/Developers/Element Studio/HTML Attributes
Developers

HTML Attributes

Oxygen generates your element's wrapper tag itself, so the HTML template can't
put attributes on it. The HTML Attributes tab is where those go — extra attributes, and
extra classes, added to the wrapper by the renderer.

The tab has two buttons: Add new HTML attribute and Add Class. Both take a name and
a Twig template, and both can react to control values.

Attributes

The Attributes pane in Element Studio

An attribute is a name plus a template that produces its value:

static function attributes()
{
    return [
        ['name' => 'href',   'template' => '{{ content.content.url }}'],
        ['name' => 'target', 'template' => '{{ content.content.open_in_new_tab ? '_blank' : '_self' }}'],
    ];
}

That's Oxygen's Container Link element: the whole reason it's a link rather than a
container is these two attributes. The template is ordinary Twig, evaluated with the same
control values the HTML template sees, so anything you can express there you can express
here.

A template that evaluates to nothing produces no attribute at all, which is how conditional
attributes are written — the Twig ternary with no else branch:

{{ design.background.lazy_load ? 'waiting' }}

Set data-bde-lazy-bg from that and the attribute appears only when lazy loading is on.

Two details of that emptiness test are worth knowing. The value is trimmed and stripped of
newlines before it's checked, so Twig's stray whitespace won't accidentally produce an
attribute. And 0 counts as a value, not as empty — an attribute whose template outputs
0 is rendered. If you genuinely want a valueless attribute in the output, such as
disabled, output the literal %%OUTPUTBLANKATTRIBUTE%%; it survives the emptiness check
and is replaced with an empty string at the end.

Attributes are the natural home for:

  • Link behaviour — href, target, rel, download
  • Accessibility — role, aria-label, aria-expanded, tabindex, all driven from
    controls so the user can fill them in
  • Hooks for your own JavaScript — a data- attribute carrying a setting your
    dependency script reads at runtime, which is cleaner than generating
    a script tag per instance

Don't add class, id or style here. Oxygen manages all three on the wrapper, and
setting them yourself fights the builder.

Classes

Add Class works the same way, with a twist: the name is the class, and the
template decides whether it's applied. A template that outputs anything at all adds the
class; one that outputs nothing doesn't.

For a class that's always present, the convention in Oxygen's own elements is the literal
string yes:

static function additionalClasses()
{
    return [['name' => 'swiper-slide', 'template' => 'yes']];
}

For a conditional one, return something only when it applies:

static function additionalClasses()
{
    return [['name' => 'bde-accordion--bordered', 'template' => '{% if design.item.style == 'bordered' %}yes{% endif %}']];
}

The template's output isn't used as the class name — only its emptiness is checked — so
yes is a convention, not a magic value.

Additional classes are how you expose modifier classes for
Default CSS to target. A card element with a style dropdown can add
card--outlined or card--filled here and style each in plain CSS, instead of generating
every variant's properties through the Twig CSS template. It keeps both files shorter.

What this doesn't cover

Attributes on elements inside your template are just markup — write them in the
HTML tab as you would in any HTML file. This tab is only ever about the wrapper
Oxygen creates for you.

Users can also add their own attributes to any element instance from the builder's element
settings, without touching your element. Those are merged in after yours, so on a name
clash the user's value wins and yours is discarded. That's the right behaviour, and it's
a reason to expose anything a user might reasonably want to change — aria-label, rel,
id-like data attributes — as a control rather than hardcoding it here.