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 drives presentation. A task card can expose completion through an attribute. A button can expose disabled. A disclosure exposes open. A dialog exposes its native open state.
Use properties for live control state and attributes for states CSS or inspection should observe. toggleAttribute() fits a present-or-absent flag. Update the button label in the same function so the action stays 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'))
Click Complete once and the card gains data-complete, the label becomes “Mark active”, and aria-pressed reads true.
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 sync 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. That is why toggleAttribute(name, boolean) reads more clearly than assigning a string.
Try this on a task card: toggle completion repeatedly and check the card attribute, button text, and aria-pressed after every click in DevTools. Reload and notice that DOM-only state is not persistence. Call setTaskComplete() 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 function.
Lesson completed