Skip to content

Properties

This lesson explains JavaScript properties in Web Components, how they differ from attributes, and how to design predictable property APIs for reusable custom elements.

What Are Web Component Properties?

Properties are JavaScript values on a custom element instance. They are the best way to pass complex data such as objects, arrays, functions, and booleans into a Web Component from application code.

Attributes are declarative and string-based. Properties are programmatic and can hold any JavaScript type. Good component APIs use both intentionally.

Concept Description
Property JavaScript value on the element instance
Attribute String value in HTML markup
Getter / Setter Controlled property access on the class
Best For Objects, arrays, functions, and rich state
Set From JavaScript, frameworks, and app logic
Main Goal A powerful and predictable component API

Basic Property Example

class UserBadge extends HTMLElement {
  connectedCallback() {
    this.render();
  }

  set user(value) {
    this._user = value;
    this.render();
  }

  get user() {
    return this._user;
  }

  render() {
    const name = this._user?.name || "Guest";
    const role = this._user?.role || "viewer";
    this.textContent = `${name} (${role})`;
  }
}

customElements.define("user-badge", UserBadge);
const badge = document.querySelector("user-badge");

badge.user = {
  name: "Alex Chen",
  role: "admin"
};

Properties are ideal when the component needs structured data that does not fit cleanly into HTML attributes.

Properties vs Attributes

Feature Property Attribute
Type Any JavaScript value String only
Set in HTML Not directly for complex values Yes, declaratively
Set in JS element.value = ... setAttribute()
Best For Objects, arrays, functions Simple config and flags
SSR Friendly Needs client hydration Available in server HTML

Getters and Setters

class AppButton extends HTMLElement {
  get disabled() {
    return this.hasAttribute("disabled");
  }

  set disabled(value) {
    this.toggleAttribute("disabled", Boolean(value));
    this.render();
  }

  get label() {
    return this.getAttribute("label") || "";
  }

  set label(value) {
    if (value == null) {
      this.removeAttribute("label");
    } else {
      this.setAttribute("label", value);
    }
    this.render();
  }
}

Getters and setters let you expose a clean property API while keeping internal state, attributes, and rendering in sync.

Boolean Properties

class ToggleSwitch extends HTMLElement {
  get checked() {
    return this.hasAttribute("checked");
  }

  set checked(value) {
    this.toggleAttribute("checked", Boolean(value));
    this.updateUI();
  }

  updateUI() {
    this.setAttribute(
      "aria-checked",
      String(this.checked)
    );
  }
}
const toggle = document.querySelector("toggle-switch");
toggle.checked = true;

Boolean properties are commonly mirrored to attributes so the component works in both HTML and JavaScript contexts.

Object and Array Properties

class DataList extends HTMLElement {
  set items(value) {
    this._items = Array.isArray(value) ? value : [];
    this.render();
  }

  get items() {
    return this._items || [];
  }

  render() {
    this.innerHTML = this.items
      .map((item) => `<li>${item.label}</li>`)
      .join("");
  }
}
list.items = [
  { id: 1, label: "Home" },
  { id: 2, label: "Settings" }
];

Use properties for collections and structured data. Attributes are a poor fit for JSON-like values in production APIs.

Function Properties

class AutocompleteInput extends HTMLElement {
  set filterFn(fn) {
    this._filterFn = typeof fn === "function" ? fn : null;
  }

  get filterFn() {
    return this._filterFn;
  }

  filterItems(items, query) {
    if (!this._filterFn) return items;
    return this._filterFn(items, query);
  }
}

Function properties are useful for advanced customization, but they should be documented clearly because they cannot be set from plain HTML attributes.

Reflecting Properties to Attributes

class StatusBadge extends HTMLElement {
  static get observedAttributes() {
    return ["variant"];
  }

  get variant() {
    return this.getAttribute("variant") || "info";
  }

  set variant(value) {
    if (value) {
      this.setAttribute("variant", value);
    } else {
      this.removeAttribute("variant");
    }
  }

  attributeChangedCallback() {
    this.render();
  }
}

Reflect important property values to attributes when you want CSS styling, SSR, or HTML-based configuration to stay in sync.

Using Properties in Frameworks

// React
<user-card user={{ name: "Alex", role: "admin" }} />

// Vue
// <user-card :user="currentUser" />

// Plain JavaScript
const card = document.querySelector("user-card");
card.user = currentUser;

Frameworks usually set properties on custom elements rather than serializing complex values into attributes. Design your API with that in mind.

Default Property Values

class PaginatedList extends HTMLElement {
  constructor() {
    super();
    this._page = 1;
    this._pageSize = 10;
    this._items = [];
  }

  get page() {
    return this._page;
  }

  set page(value) {
    this._page = Number(value) || 1;
    this.render();
  }
}

Initialize default values in the constructor so the component behaves predictably before any property is set by the consumer.

Common Property Patterns

Pattern Use Case Example
Reflected boolean Disabled, open, checked states element.open = true
Object property User, config, record data element.user = { ... }
Array property Lists, options, menu items element.items = []
Callback property Custom render or filter logic element.filterFn = fn

When to Use Properties

  • You need to pass objects, arrays, or functions into a component.
  • Framework apps set component state programmatically.
  • You want a rich JavaScript API beyond HTML attributes.
  • Boolean or numeric values are controlled from app logic.
  • Internal rendering depends on structured data.
  • You are designing a reusable library for multiple codebases.

Common Property Use Cases

  • User, product, and record objects in display components.
  • Item lists in menus, tabs, and data tables.
  • Open, disabled, and checked states in interactive widgets.
  • Configuration objects for charts, maps, and editors.
  • Callback hooks for custom filtering or formatting.
  • Framework-driven state in React, Vue, and Angular apps.

Property Best Practices

  • Use properties for rich JavaScript values and attributes for simple config.
  • Expose getters and setters for important public properties.
  • Initialize defaults in the constructor.
  • Reflect key values to attributes when HTML or CSS needs them.
  • Validate and normalize values inside setters.
  • Document property types, defaults, and sync behavior.
  • Trigger rendering or state updates from setter changes.

Common Property Mistakes

  • Using attributes for objects and arrays.
  • Exposing public state only as internal fields without getters/setters.
  • Not updating the UI when a property changes.
  • Assuming HTML attributes automatically become typed properties.
  • Forgetting default values for unset properties.
  • Reflecting every property to attributes unnecessarily.
  • Creating property names that do not match framework conventions.

Key Takeaways

  • Properties are the JavaScript API of a Web Component.
  • Use them for objects, arrays, booleans, and functions.
  • Getters and setters help keep state, attributes, and UI in sync.
  • Frameworks prefer properties for complex component inputs.
  • Good property design makes custom elements easier to reuse.

Pro Tip

Design your public API in two layers: simple values through attributes, rich app state through properties. That gives you both declarative HTML usage and powerful framework integration.