CSS Custom State - Interactive CSS Lab | IconVault
Learn the :state() pseudo-class with CustomStateSet. Toggle --on, --invalid and --loading on a live custom element. Free, in your browser.
CSS Custom State
:state() custom states with CustomStateSet, no class hacks
Toggle custom states
Each toggle calls internals.states.add() on the element. CSS reads them with :state(), exactly like :checked or :disabled on native inputs.
Live demo: a custom switch
Clicking the switch itself toggles --on too (wired in connectedCallback). Compare with the class-hack era: no more .is-on leaking into your HTML.
Copy the code
class DemoToggle extends HTMLElement {
static formAssociated = true;
#internals = this.attachInternals();
setState(name, on) {
on ? this.#internals.states.add(name)
: this.#internals.states.delete(name);
}
connectedCallback() {
this.addEventListener("click", () =>
this.setState("--on", !this.#internals.states.has("--on")));
}
}
customElements.define("demo-toggle", DemoToggle);
/* Custom element states, styled like native pseudo-classes */
demo-toggle { display: block; }
/* The switch track */
demo-toggle .track {
width: 56px; height: 30px; border-radius: 999px;
background: #cbd5e1; transition: background 0.2s;
position: relative;
}
demo-toggle .knob {
position: absolute; top: 3px; left: 3px;
width: 24px; height: 24px; border-radius: 999px;
background: white; transition: left 0.2s;
}
/* Custom states drive the styling: no .on classes needed */
demo-toggle:state(--on) .track { background: #0d9488; }
demo-toggle:state(--on) .knob { left: 29px; }
demo-toggle:state(--invalid) { outline: 2px solid #ef4444; outline-offset: 4px; border-radius: 8px; }
demo-toggle:state(--loading) .knob { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }5 of 5 free copies left.
About this tool
IconVault's CSS Custom State lab teaches the :state() pseudo-class with a live custom element you can control. Toggle the --on, --invalid and --loading states to watch a switch restyle itself through CustomStateSet, exactly like native :checked or :disabled work on built-in inputs. The page shows the custom element definition, the attachInternals() wiring, and the CSS selectors side by side, so the whole pattern clicks. It is free and runs fully in your browser.
Before custom states, component authors faked element states with classes like .is-open, leaking implementation details into HTML and fighting the cascade. With ElementInternals.states, a custom element exposes real states that CSS reads with :state(--name), keeping markup clean and selectors semantic. The lab feature-detects :state() support, runs the real thing on Chrome 133+ and recent Firefox, and shows an honest class-based simulation on older browsers, plus copyable code for both the element and the stylesheet.
Frequently asked questions
Popular searches
More tools
Algorithm Visualizer
Watch sorting and searching animate step by step
Alt Text Helper
Write better image alt text with live feedback
ARIA Reference
Searchable ARIA roles and attributes guide
Array Visualizer
Step through map, filter, reduce like a debugger
Coming soon
CLI
Coming soon
Figma plugin
Coming soon
VS Code extension
Coming soon
Chrome extension
Coming soon
AI icon generator
Coming soon
Team workspaces
Coming soon