## CSS Toggle States This is heavily based on a previous proposal by Tab; the active work happening in OpenUI around popups, tabs, and accordions; and a work-in-progress proposal by Nicole Sullivan & Robert Flack. The key feature here is that named toggles work in the same way as CSS counters: - They flow to consecutive siblings, and also inherit to descendants - They can be updated or reset by either siblings or descendants - Proposal at <https://tabatkins.github.io/css-toggle/> ## Non-exclusive Accordion (with or without cards) ```html <accordion> <!-- Express initial states for a given toggle in html, optionally with one of multiple states (e.g. `--card 3`) --> <card toggled="--card"> <tab>...</tab> <content>...</content> </card> <card> <tab>...</tab> <content>...</content> </card> <card> <tab>...</tab> <content>...</content> </card> </accordion> ``` ```css tab { toggle: --card 2; /* shorthand for: * toggle-states: --card 2; * toggle-set: --card; */ } content:checked(--card) { /* Toggles use counter scoping rules * So this sees the toggle (and value) established * by its previous sibling <tab> */ … } ``` ## Accordion again, but with unpredictable tab/content order ```html <accordion> <card> <content>...</content> <tab>...</tab> </card> <card> <tab>...</tab> <content>...</content> </card> <card> <tab>...</tab> <content>...</content> </card> </accordion> ``` ```css card { toggle-states: --card 2; } tab { /* toggle established by parent, * so <tab> just opts into *manipulating* * the toggle */ toggle-set: --card; } content:checked(--card) { /* This time, the toggle comes from the parent } ``` ## Details element ```html <details2> <summary>...</> <content>...</> </details2> <style> details2 { toggle-states: --show 2; } summary { toggle-set: --show; } content { display: none; } content:checked(--show) { display: block; } ``` ## Self-contained Checkbox w/ Named States ```html <check-box></> <style> check-box { toggle-self: 3 off on unknown; /* toggle-self limits the toggle's scope * to just the element itself. * We *think* this is just a safety improvement, * not actually a needed new ability. */ } check-box:checked(self off) {...} ``` ## Tabs / Exclusive Accordion (with or without cards) ```html <tabs> <card> <tab>...</tab> <content>...</content> </card> <card> <tab>...</tab> <content>...</content> </card> <card> <tab>...</tab> <content>...</content> </card> </tabs> ``` ```css tabs { toggle-group: --show 2; /* -group establishes a scope for sub-counters, * of which only one can be active at a time * Same grammar as -states, so all sub-counters are identical. */ } tab { toggle-item: --show; /* creates a sub-counter tied to the --show group */ /* because --show is a group, only one can be active */ } content:checked(--show) { /* sees the --show counter from its sibling */ } ``` ## Arbitrary toggler element & target positions ```html <html toggle-scope="colors"> <button toggle-btn="colors">…</> <section toggle-target="colors">…</> <style> [toggle-scope] { toggle-states: attr(toggle-scope) [...]; /* toggles can be named by a string * instead of a custom ident */ } button { toggle-set: attr(toggle-btn); } [toggle-target]:checked(attr(toggle-target)) { /* Requires some magic here, * but attr information *is* known * during selector evaluation. * Hopefully okay? */ } ``` ## Tabs up front ❌ The code here represents some of our early attempts, but we do not consider this use-case solved -- or part of the current proposal. ```html <tabs> <tab>...</tab> <tab>...</tab> <tab>...</tab> <content>...</content> <content>...</content> <content>...</content> </tabs> <style> /* tab-bar mode */ tabs { display: grid; grid-template-rows: auto 1fr; counter-reset: tabs contents; toggle-states: --tab group 2; } tabs::grid-cell(1 / 1) { area-name: foo; display: flex; } tab { flow-into-grid-area: foo; counter-increment: tabs; toggle-set: --tab counter(tabs); } content { grid-row: 2; counter-increment: contents; toggle-read: --tab counter(contents); } .radiogroup { toggle-states: --radio group 2; } .radio { toggle-set: --radio; toggle-read: --radio; } ``` Is this a potential solution? ```css tabs { toggle-group: --show 2; counter-reset: tabs contents; } tab { toggle-item: --show counter(tabs); /* toggle-item could generically allow named items, * and accept counter() as one way to assign those names */ counter-increment: tabs; } content { counter-increment: contents; } content:checked(--show counter(contents)) { /* sees the --show counter from its sibling */ } ``` ## Open Questions & Potential Issues - All syntax needs bikeshedding… - clarity around toggle-names, state-counts, and state-names - is `:checked()` the right syntax for a pseudo? - what are the default states, and can we name them (e.g. on/off)? - Can a11y be built-in and handled automagically? - different toggle types (show/hide, files, etc) may need different a11y handling, - ways to opt-into those semantics? - **it should not be easy to create in-accessible interfaces** - How do we handle content-visibility? - Especially for linking into hidden tabs, etc? - How does it interact with animations & transitions? - Can state be maintained across navigation, like form controls?