Native rendering and state
Reflect state with DOM attributes
Represent task state with native properties and meaningful attributes so CSS, JavaScript, and tests see one result.
For a small interface, the DOM can hold the state that directly controls presentation. A task card can expose completion through an attribute, a button through disabled, a disclosure through open, and a dialog through its native state.
Use properties for live control state and attributes for states that CSS or inspection should observe. toggleAttribute() is ideal for a present-or-absent flag. Update the button label in the same function so the action remains truthful after each change.
function setTaskComplete(card, complete) {
card.toggleAttribute("data-complete", complete)
const button = card.querySelector("[data-action=complete]")
button.textContent = complete ? "Mark active" : "Complete"
button.setAttribute("aria-pressed", String(complete))
}
setTaskComplete(card, !card.hasAttribute("data-complete"))
Only use aria-pressed when the button really behaves as a toggle. The visible label may change or stay stable, but the pressed state must match the action. ARIA reports a contract; it does not implement the state for you.
Do not keep three independent copies such as task.complete, data-complete, and .is-complete unless you have a clear synchronization boundary. One canonical record plus one render function is safer when state also persists or arrives from a server.
Native Boolean attributes are based on presence. hidden="false" is still present and therefore hidden. Remove the attribute or set the property to false. This is why toggleAttribute(name, boolean) reads more accurately than assigning a string.
Toggle a task repeatedly and assert the card attribute, button text, and aria-pressed value after every click in DevTools. Reload and note that DOM-only state is not persistence. Call the function directly with both true and false so the result does not depend on a previous click. If CSS shows completion while the button announces false, route every update through one setTaskComplete() function.
Lesson completed