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