const lab = document.querySelector('[data-cascade-lab]');
const form = lab.querySelector('form');
const button = lab.querySelector('.demo-button');
const winner = lab.querySelector('[data-winner]');
const explain = lab.querySelector('[data-explain]');
const rules = Array.from(lab.querySelectorAll('[data-rule]'));
const outcomes = {
source: {
rule: 'utility',
title: 'Utility wins by source order.',
explain: 'The muted utility appears later and overrides the destructive state, so the UI loses important meaning.',
},
specificity: {
rule: 'specificity',
title: 'The escalated selector wins.',
explain: 'The symptom is fixed, but the new selector is expensive. The next override now has to beat an ID-level rule.',
},
owner: {
rule: 'owner',
title: 'The component-owned state wins.',
explain: 'The destructive intent is represented as component state, so the important visual meaning is owned where the behavior lives.',
},
noUtility: {
rule: 'state',
title: 'The component state wins.',
explain: 'Without the competing utility, the normal state selector is enough. No escalation is needed.',
},
};
function render() {
const data = new FormData(form);
const strategy = data.get('strategy') || 'source';
const hasUtility = data.get('utility') === 'on';
const outcome = hasUtility ? outcomes[strategy] : outcomes.noUtility;
lab.dataset.strategy = strategy;
lab.dataset.utility = hasUtility ? 'on' : 'off';
button.classList.toggle('u-muted', hasUtility);
winner.textContent = outcome.title;
explain.textContent = outcome.explain;
rules.forEach((rule) => {
const active = rule.dataset.rule === outcome.rule;
rule.toggleAttribute('data-winner', active);
if (active) {
rule.setAttribute('aria-current', 'true');
} else {
rule.removeAttribute('aria-current');
}
});
}
form.addEventListener('input', render);
render();
What this demo is for
Use this demo when an override fails and you need to decide whether the fix belongs in selector weight, source order, cascade layers, or component state.
Try this
Start with source order and the muted utility enabled: the destructive button loses its visual intent.
Switch to selector escalation and notice that it fixes the symptom while creating a more expensive selector.
Switch to component-owned state to keep the important state rule explicit instead of relying on a stronger selector.