Attributes
This lesson explains Attributes with clear examples, use cases, and best practices so you can build reusable Web Components confidently.
Web Components Attributes Overview
Attributes are HTML-based configuration values used to control the
appearance, behavior, state, and accessibility of Web Components. They are
written directly on the custom element in markup and are always stored as
strings in the DOM.
Web Components commonly use attributes for labels, variants, sizes,
disabled states, selected states, open states, themes, IDs, ARIA values,
and other simple public configuration.
Web Components Attributes List
- 1. Use attributes for simple HTML configuration.
Attributes make a custom element easy to configure directly in markup.
<my-alert
type="success"
message="Saved successfully">
</my-alert>
- 2. Remember that attributes are strings.
Even when an attribute looks like a number or boolean, the browser stores
it as a string.
const counter =
document.querySelector("my-counter");
console.log(
counter.getAttribute("count")
);
// "5"
- 3. Read attributes with
getAttribute().
Use getAttribute() to read string values from the custom
element.
const message =
this.getAttribute("message") || "";
const type =
this.getAttribute("type") || "info";
- 4. Set attributes with
setAttribute().
Use setAttribute() to update an attribute from JavaScript.
this.setAttribute(
"type",
"warning"
);
this.setAttribute(
"message",
"Please check your input."
);
- 5. Remove attributes with
removeAttribute().
Removing an attribute is commonly used for boolean states like
disabled, open, selected, and
hidden.
this.removeAttribute("disabled");
this.removeAttribute("open");
- 6. Check attributes with
hasAttribute().
Boolean attributes should usually be checked by presence.
const isDisabled =
this.hasAttribute("disabled");
const isOpen =
this.hasAttribute("open");
- 7. Use boolean attributes by presence.
A boolean attribute is true when present and false when absent.
<my-button disabled>
Save
</my-button>
get disabled() {
return this.hasAttribute("disabled");
}
- 8. Do not use
disabled="false" as false.
Boolean attributes are true when present, even if the value is
"false".
<my-button disabled="false">
Save
</my-button>
In this example, disabled is still considered present.
- 9. Convert numeric attributes manually.
Use Number(), parseInt(), or validation logic
when reading numeric attributes.
get count() {
return Number(
this.getAttribute("count") || 0
);
}
get maxCount() {
return Number(
this.getAttribute("max-count") || 10
);
}
- 10. Validate attribute values.
Restrict public attributes such as variant,
size, and type to known values.
get variant() {
const allowed =
["primary", "secondary", "danger"];
const value =
this.getAttribute("variant");
return allowed.includes(value)
? value
: "primary";
}
- 11. Use kebab-case attribute names.
HTML attributes should be lowercase or kebab-case. JavaScript properties
can use camelCase.
<my-counter
max-count="10"
selected-index="0">
</my-counter>
- 12. Map attributes to properties.
Use getters and setters to provide a JavaScript-friendly API.
get selectedIndex() {
return Number(
this.getAttribute("selected-index") || 0
);
}
set selectedIndex(value) {
this.setAttribute(
"selected-index",
String(value)
);
}
- 13. Observe important attributes.
A custom element reacts to attribute changes only when the attribute is
listed in observedAttributes.
static get observedAttributes() {
return [
"type",
"message",
"disabled",
"open"
];
}
- 14. Use
attributeChangedCallback().
This lifecycle method runs when an observed attribute is added, changed,
or removed.
attributeChangedCallback(
name,
oldValue,
newValue
) {
if (oldValue === newValue) {
return;
}
this.update();
}
- 15. Handle removed attributes.
When an attribute is removed, newValue becomes
null.
attributeChangedCallback(name, oldValue, newValue) {
if (newValue === null) {
console.log(name + " removed");
}
}
- 16. Provide default attribute values.
Getters should return safe defaults when attributes are missing.
get type() {
return this.getAttribute("type") || "info";
}
get message() {
return this.getAttribute("message") || "";
}
- 17. Use attributes for styling hooks.
Reflected attributes are useful with :host() selectors
inside Shadow DOM.
:host([type="success"]) {
display: block;
}
:host([disabled]) {
opacity: 0.6;
}
- 18. Use attributes for accessible state.
Attributes can help expose state to assistive technologies using ARIA.
<my-accordion
aria-expanded="false"
aria-controls="panel-1">
</my-accordion>
- 19. Keep ARIA attributes synchronized.
When component state changes, update related ARIA attributes too.
set open(value) {
if (value) {
this.setAttribute("open", "");
} else {
this.removeAttribute("open");
}
this.setAttribute(
"aria-expanded",
String(Boolean(value))
);
}
- 20. Avoid attributes for complex data.
Arrays, objects, functions, dates, maps, sets, and class instances
should usually be passed as properties.
const table =
document.querySelector("my-table");
table.rows =
[
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
- 21. Avoid large JSON attributes.
JSON attributes are fragile and harder to maintain. Prefer JavaScript
properties for structured data.
<my-table data='[{"id":1}]'>
</my-table>
- 22. Parse JSON attributes safely when needed.
If a component supports JSON attributes, handle parsing errors.
get config() {
try {
return JSON.parse(
this.getAttribute("config") || "{}"
);
} catch (error) {
return {};
}
}
- 23. Use
data-* attributes for custom metadata.
data-* attributes store simple custom metadata and are
available through dataset.
<my-card
data-track-id="billing-card">
</my-card>
const trackId =
this.dataset.trackId;
- 24. Reflect useful public state.
Reflecting state to attributes helps styling, debugging, testing, and
documentation.
set selected(value) {
if (value) {
this.setAttribute("selected", "");
} else {
this.removeAttribute("selected");
}
}
- 25. Avoid reflecting high-frequency state.
Do not reflect values that change rapidly, such as mouse position,
scroll position, animation frame data, or drag coordinates.
this._dragX =
event.clientX;
this._dragY =
event.clientY;
- 26. Use attributes for server-rendered components.
Attributes make Web Components easier to render from HTML templates,
CMS pages, static sites, and server-rendered apps.
<my-product-card
product-id="101"
title="Laptop | PHPKINGDOM"
price="999">
</my-product-card>
- 27. Initialize from existing attributes.
A component should read attributes that already exist in markup when it
connects.
connectedCallback() {
if (!this.shadowRoot) {
this.attachShadow(
{ mode: "open" }
);
this.render();
}
this.updateFromAttributes();
}
- 28. Test runtime attribute updates.
Components should update when attributes change after initial render.
const alert =
document.querySelector("my-alert");
alert.setAttribute(
"type",
"warning"
);
alert.setAttribute(
"message",
"Check required fields."
);
- 29. Document supported attributes.
Public components should document attribute name, type, allowed values,
default value, whether it reflects, and related property name.
- 30. Keep attributes predictable.
A good attribute API is simple, consistent, typed through conversion,
validated, accessible, and easy to use from plain HTML.
Complete Web Component Attributes Example
This example shows string, boolean, numeric, and ARIA-related attributes
with safe defaults and update logic.
class MyAlert extends HTMLElement {
static get observedAttributes() {
return [
"type",
"message",
"dismissible",
"open"
];
}
constructor() {
super();
this.attachShadow(
{ mode: "open" }
);
this.shadowRoot.innerHTML =
`
<style>
:host {
display: block;
}
:host(:not([open])) {
display: none;
}
.alert {
border: 1px solid #dbe5f1;
border-radius: 0.75rem;
padding: 1rem;
}
:host([type="success"]) .alert {
border-color: #bbf7d0;
}
:host([type="danger"]) .alert {
border-color: #fecaca;
}
</style>
<div class="alert" role="status">
<span class="message"></span>
<button type="button" hidden>
Dismiss
</button>
</div>
`;
}
connectedCallback() {
if (!this.hasAttribute("open")) {
this.setAttribute("open", "");
}
this.update();
this.shadowRoot
.querySelector("button")
.addEventListener("click", () => {
this.removeAttribute("open");
this.dispatchEvent(
new CustomEvent(
"alert-dismiss",
{
bubbles: true,
composed: true
}
)
);
});
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue === newValue) {
return;
}
this.update();
}
get type() {
const allowed =
["info", "success", "warning", "danger"];
const value =
this.getAttribute("type");
return allowed.includes(value)
? value
: "info";
}
set type(value) {
this.setAttribute(
"type",
value
);
}
get message() {
return this.getAttribute("message") || "";
}
set message(value) {
this.setAttribute(
"message",
String(value)
);
}
get dismissible() {
return this.hasAttribute("dismissible");
}
set dismissible(value) {
if (value) {
this.setAttribute("dismissible", "");
} else {
this.removeAttribute("dismissible");
}
}
get open() {
return this.hasAttribute("open");
}
set open(value) {
if (value) {
this.setAttribute("open", "");
} else {
this.removeAttribute("open");
}
}
update() {
if (!this.shadowRoot) {
return;
}
const message =
this.shadowRoot.querySelector(".message");
const button =
this.shadowRoot.querySelector("button");
message.textContent =
this.message;
button.hidden =
!this.dismissible;
this.setAttribute(
"aria-hidden",
String(!this.open)
);
}
}
customElements.define(
"my-alert",
MyAlert
);
<my-alert
type="success"
message="Profile saved successfully."
dismissible
open>
</my-alert>
const alert =
document.querySelector("my-alert");
alert.setAttribute(
"type",
"warning"
);
alert.message =
"Please review your changes.";
alert.dismissible =
true;
Common Web Component Attributes Table
| Attribute | Type | Example | Common Use |
label | String | label="Save" | Button text or field label. |
variant | String | variant="primary" | Visual style. |
size | String | size="large" | Component size. |
disabled | Boolean | disabled | Disable interaction. |
open | Boolean | open | Expanded or visible state. |
selected | Boolean | selected | Selected state. |
count | Number string | count="5" | Numeric configuration. |
aria-label | String | aria-label="Close" | Accessible name. |
data-* | String | data-track-id="card" | Custom metadata. |
Common Web Components Attribute Mistakes
-
Forgetting that attributes are always strings.
-
Using
disabled="false" and expecting it to mean false.
-
Not listing important attributes in
observedAttributes.
-
Using attributes for large objects or arrays.
-
Not validating attribute values.
-
Not providing default values for missing attributes.
-
Not syncing attributes with internal Shadow DOM controls.
-
Forgetting to update ARIA attributes when state changes.
-
Reflecting high-frequency internal state into attributes.
-
Not documenting public attributes for component consumers.
Web Components Attribute Best Practices
-
Use attributes for simple public configuration.
-
Use properties for complex JavaScript data.
-
Use boolean attributes by presence.
-
Use kebab-case for multi-word attributes.
-
Convert numeric strings safely.
-
Validate allowed values such as variants and sizes.
-
Observe only attributes that affect rendering or behavior.
-
Keep accessibility attributes synchronized.
-
Test initial attributes and runtime changes.
-
Document attribute names, types, defaults, and allowed values.
Pro Tip
Use attributes for values that make sense in HTML, such as
variant, size, disabled,
open, and label. Use properties for real
JavaScript data such as arrays, objects, callbacks, and service instances.