Ad – 728×90
🦉 Dynamic Templates

OWL JS Dynamic Attributes – t-att, t-att-class, t-att-style

Static HTML attributes are fine for values that never change, but most real-world components need attributes that respond to state or props. OWL provides two directives for this: t-att-[name] sets a single attribute to a JavaScript expression, and t-att sets multiple attributes from an object. The most commonly used variant is t-att-class, which accepts an object of { "className": boolExpr } for conditional CSS classes. This lesson covers every form of dynamic attribute binding with practical examples.

⏱️ 16 min read 🎯 Beginner 📅 Updated 2026 👁️ Lesson 4 of 5

t-att-[name] — Single Attribute

Replace any static attribute with t-att-[name] and set its value to a JavaScript expression. The expression is evaluated in the component's scope.

XML – t-att-* examples
<!-- src and alt from props -->
<img t-att-src="props.avatarUrl" t-att-alt="props.userName + ' avatar'"/>

<!-- href built from expression -->
<a t-att-href="'/products/' + props.productId">View product</a>

<!-- Conditional disabled -->
<button t-att-disabled="state.isLoading || !state.isValid">Submit</button>

<!-- Tabindex from state -->
<div t-att-tabindex="state.isInteractive ? 0 : -1"></div>

<!-- data-* attributes -->
<div t-att-data-id="item.id" t-att-data-category="item.category"></div>

<!-- Input type from props -->
<input t-att-type="props.inputType" t-att-placeholder="props.placeholder"/>
ℹ️
Boolean attributes (disabled, checked, readonly, required)

For boolean HTML attributes, t-att-disabled="false" removes the attribute entirely (correct behavior). t-att-disabled="true" adds disabled="disabled". You never need to set disabled="false" in HTML — absence of the attribute means not disabled. OWL handles this correctly for all boolean attributes.

t-att-class — Conditional CSS Classes

t-att-class is the most commonly used dynamic attribute in OWL. It accepts three forms:

Object syntax (recommended)

XML – t-att-class object syntax
<!-- Each key is a class name, each value is a boolean expression -->
<div t-att-class="{
  'card':          true,
  'card--active':  state.isActive,
  'card--error':   state.hasError,
  'card--loading': state.isLoading,
  'card--large':   props.size === 'large'
}"></div>

<!-- Mix with static class attribute -->
<button class="btn" t-att-class="{
  'btn--primary':   props.variant === 'primary',
  'btn--secondary': props.variant === 'secondary',
  'btn--disabled':  props.disabled
}">
  <t t-esc="props.label"/>
</button>

String expression

XML – t-att-class string expression
<!-- Build class name string dynamically -->
<span t-att-class="'badge badge--' + props.status"/>
<!-- Renders: class="badge badge--active" (or "badge--pending", etc.) -->

<!-- Ternary -->
<div t-att-class="state.isOpen ? 'panel panel--open' : 'panel panel--closed'"/>
Ad – 336×280

t-att-style — Inline Styles

t-att-style accepts either a CSS string or a style object:

XML – t-att-style
<!-- String form -->
<div t-att-style="'background-color: ' + props.color + '; opacity: ' + state.opacity"/>

<!-- Object form (keys are camelCase CSS property names) -->
<div t-att-style="{
  backgroundColor: props.color,
  opacity:         state.opacity,
  transform:       'translateX(' + state.offsetX + 'px)',
  display:         state.visible ? 'block' : 'none'
}"/>

<!-- Progress bar width -->
<div class="progress-bar">
  <div
    class="progress-fill"
    t-att-style="{ width: state.progress + '%' }"
  />
</div>

<!-- Avatar with dynamic background image -->
<div class="avatar" t-att-style="{
  backgroundImage: 'url(' + props.avatarUrl + ')',
  width:           props.size + 'px',
  height:          props.size + 'px'
}"/>

t-att — Multiple Attributes from Object

When you need to set many attributes at once from a computed object, use t-att (without a suffix):

XML – t-att spread
<!-- All attributes from an object expression -->
<input t-att="inputAttrs"/>
JavaScript – computed attrs object
class DynamicInput extends Component {
  get inputAttrs() {
    return {
      type:        this.props.type || "text",
      placeholder: this.props.placeholder || "",
      disabled:    this.state.isLoading,
      maxlength:   this.props.maxLength,
      autocomplete: "off",
    };
  }

  static template = xml`
    <input t-att="inputAttrs"/>
  `;
}

Accessible ARIA Attributes

ARIA attributes are critical for accessibility and are commonly dynamic — they should reflect current state:

XML – dynamic ARIA attributes
<!-- Accordion button -->
<button
  t-att-aria-expanded="state.isOpen ? 'true' : 'false'"
  t-att-aria-controls="'panel-' + props.id"
  t-on-click="toggle"
>
  <t t-esc="props.title"/>
</button>
<div
  t-att-id="'panel-' + props.id"
  t-att-aria-hidden="state.isOpen ? 'false' : 'true'"
  t-if="state.isOpen"
>
  <t t-slot="default"/>
</div>

<!-- Loading spinner -->
<div
  role="status"
  t-att-aria-label="state.isLoading ? 'Loading…' : 'Content loaded'"
  t-att-aria-busy="state.isLoading ? 'true' : 'false'"
></div>

<!-- Tab panel -->
<button
  role="tab"
  t-att-aria-selected="props.activeTab === props.tabId ? 'true' : 'false'"
  t-att-tabindex="props.activeTab === props.tabId ? 0 : -1"
>
  <t t-esc="props.label"/>
</button>

Real-World Component Patterns

JavaScript – Button component with all dynamic attrs
class Button extends Component {
  static template = xml`
    <button
      t-att-class="{
        'btn':              true,
        'btn--primary':     props.variant === 'primary',
        'btn--secondary':   props.variant === 'secondary',
        'btn--danger':      props.variant === 'danger',
        'btn--loading':     props.loading,
        'btn--icon-only':   !props.label
      }"
      t-att-disabled="props.disabled || props.loading"
      t-att-type="props.type || 'button'"
      t-att-aria-busy="props.loading ? 'true' : 'false'"
      t-att-aria-label="props.ariaLabel || props.label"
      t-on-click="props.onClick"
    >
      <span t-if="props.loading" class="spinner" aria-hidden="true"/>
      <span t-if="props.icon" t-esc="props.icon" aria-hidden="true"/>
      <span t-if="props.label" t-esc="props.label"/>
    </button>
  `;

  static props = {
    label:     { type: String, optional: true },
    variant:   { type: String, optional: true },
    type:      { type: String, optional: true },
    disabled:  { type: Boolean, optional: true },
    loading:   { type: Boolean, optional: true },
    icon:      { type: String, optional: true },
    ariaLabel: { type: String, optional: true },
    onClick:   { type: Function, optional: true },
  };

  static defaultProps = {
    variant:  "primary",
    disabled: false,
    loading:  false,
    onClick:  () => {},
  };
}

📋 Summary

  • t-att-[name]="expression" sets any attribute dynamically — works for all HTML attributes including src, href, disabled, tabindex, data-*, aria-*.
  • t-att-class="{className: boolExpr}" — object syntax adds/removes classes conditionally. OWL merges with any static class attribute on the same element.
  • t-att-class="'base ' + conditionalPart" — string expression for dynamic class names (BEM modifier pattern).
  • t-att-style="{camelCaseProp: value}" — object syntax for inline styles. Key names are camelCase JavaScript property names.
  • t-att="{attr1: val1, attr2: val2}" — set multiple attributes from a computed object in one directive.
  • For boolean attributes (disabled, required, checked): a falsy expression removes the attribute entirely — the correct HTML behavior.
  • Always add dynamic ARIA attributes to interactive components for accessibility.

🏋️ Exercise

Build an accessible Accordion component using dynamic attributes:

  1. Props: items — array of { id, title, content }.
  2. State: openId — the ID of the currently open panel (or null).
  3. For each item render a <button> header and a content panel. Use t-att-aria-expanded, t-att-aria-controls, t-att-id, and t-att-aria-labelledby for full ARIA compliance.
  4. Use t-att-class with object syntax to add an is-open class to the active panel header and content.
  5. Use t-att-style to animate the max-height of the content panel (0 when closed, a fixed value when open).
  6. Clicking an open item closes it; clicking another opens it and closes the previous one.

Frequently Asked Questions

Can I use both class and t-att-class on the same element? +

Yes — OWL merges them. The static class attribute always applies; t-att-class adds or removes classes on top of it. For example, class="btn" t-att-class="{ 'btn--active': isActive }" always has the btn class and conditionally has btn--active. This is the standard pattern for base classes + modifier classes.

What is the difference between t-att-disabled="false" and not having the attribute? +

In the resulting HTML, they are equivalent — both mean "not disabled". OWL removes attributes whose expression evaluates to false, null, or undefined. This means t-att-disabled="state.isLoading" correctly removes the disabled attribute when isLoading is false, and adds it when true. You never need a separate t-if to conditionally add boolean attributes.

Why use t-att-style instead of a static style attribute? +

A static style attribute can only hold a fixed string — you cannot embed JavaScript expressions in it. t-att-style lets the style values come from state, props, or computed values. For example, a progress bar's width must change dynamically: t-att-style="{ width: state.progress + '%' }". Prefer CSS classes over inline styles when possible — use t-att-style only for values that genuinely cannot be expressed with predefined classes (like exact pixel positions, user-defined colors, or computed widths).