This Proposal is Deprecated
- The style part of the problem — responding to state — is conditional CSS that can be handled with container style queries.
- The issue of creating toggles should be addressed in other parts of the platform with features like invoker commands.
In retrospect, I'm afraid this exploration (driven by Chrome) acted as a distraction from more viable solutions -- while requiring extra effort from accessibility experts to document all the issues with our approach.
What are CSS Toggles?
The goal of this (work in progress) feature is to make it possible for CSS to manage presentational state for patterns such as tabs/accordians, carousels, color modes, etc. There are still many questions to be answered around the scope, syntax, and (most importantly) accessibility of a feature like this in CSS.
This polyfill is designed to help us explore those questions. It implements the draft spec syntax as currently written, where possible -- in addition to some of the extensions proposed in our explainer.
The current polyfill implementation is a naive attempt to uncover and document any issues in the spec — which means that several of the following demos are currently inaccessible. While we hope that some of those issues can be resolved here in the polyfill, others may require changes to the spec itself, or access to browser-internals that cannot be polyfilled well using JS.
We're excited for you to play with this, suggest additional use-cases, help uncover undocumented issues, and provide us with feedback:
🔗 Global color toggle
html { toggle-root: mode [auto light dark]; } html:toggle(mode light) { ... } html:toggle(mode dark) { ... } .mode-btn { toggle-trigger: mode; }
🔗 Binary self-toggle switches
Issue 20: There's a conflict between the HTML listitem role and the added ARIA role of 'button' used to make these interactive.
- write an explainer
- draft a specification
- create a polyfill
- make a demo page
.todo li { toggle: todo self; list-style-type: '❌ '; } .todo li:toggle(todo) { list-style-type: '✅ '; }
🔗 Accordion/disclosure components
The goal of toggle-visibility
is for browsers to handle accessibility
and discoverability by default.
We'll keep working to improve the polyfill a11y as well,
but may not be able to achieve the same results.
Issue 13:
While aria-expanded
may work in some situations,
it has the same issues listed above with conflicting semantics/roles.
- Establish a toggle
.accordion>dt { toggle: glossary; }
- Toggle item visibility
.accordion>dd { toggle-visibility: toggle glossary; }
- Style the summary
.accordion>dt::marker { content: '👉🏽 '; } .accordion>dt:toggle(glossary) { background-color: var(--brand); color: var(--bg); } .accordion>dt:toggle(glossary)::marker { content: '👇🏽 '; }
🔗 Tree view
Issue 23: Does 'tree view' require different semantics than simply 'nested disclosures' (which may better describe the current behavior)?
.nested { toggle: tree; } .nested+ul { toggle-visibility: toggle tree; }
🔗 Tabs or exclusive accordions
- Issue 9: Not possible to change initial state for one toggle in a group.
-
Issue 13:
In this case,
the expected a11y handling requires the
tab
role andaria-selected
. Is there a way to clearly support both disclosure and tab a11y using this shared syntax?
panel-tab { toggle: tab 1 at 0 group sticky; grid-row: tab; }
panel-tab:toggle(tab) { background-color: var(--callout); } panel-card { toggle-visibility: toggle tab; grid-area: card; }
🔗 Named states
Issue 21: These act like radio buttons, but the proper a11y handling is not obvious from the syntax.
.colors { toggle-root: colors [grape green blue red] at blue; } .colors button { toggle-trigger: colors; } /* for each color */ .colors button.grape { toggle-trigger: colors grape; } .show-colors:toggle(colors grape) { background-color: var(--grape-9); }
🔗 State machine transitions
This functionality & syntax is proposed in the explainer as syntax sugar on top of the existing functionality. However, it's not yet clear that there are entirely presentational (CSS-only) use-cases. If you have ideas, we'd love to hear from you. The following example would involve JS in production.
Issue 22: It is not clear in the a11y tree which 'transitions' are currently allowed/disabled, and the resulting generated content is not propery announced or selectable.
@machine request { idle { try: loading; } loading { resolve: success; reject: failure; reset: idle; } failure { try: loading; reset: idle; } success { reset: idle; } } /* establish a toggle based on the machine */ .request { toggle-root: machine(request); } /* the individual trigger buttons call 'transitions' */ .request button[data-do="try"] { toggle-trigger: request do(try); }