1. Introduction
This section is not normative.
Some user-interface languages define elements which can have "toggleable state",
which can be modified by user interaction and selected using CSS Selectors.
For example, in HTML, the <input type=checkbox>
has a "checked" state
which toggles between true and false when the user activates the element,
and which is selected by the :checked pseudoclass.
<ul class='ingredients'> <li><label><input type=checkbox><span>1 banana</span></label> <li><label><input type=checkbox><span>1 cup blueberries</span></label> ... </ul> <style> input[type='checkbox'] { display: none; } input[type='checkbox']:checked + span { color: silver; text-decoration: line-through; } </style>
In this markup, one can cross out ingredients as they’re used in the recipe by simply clicking on them.
This module generalizes this ability and allows it to be applied to any element via CSS.
Elements can be declared to have toggleable state,
with any number of states that can be toggled between.
Multiple elements can share access to the same toggleable state,
similar to HTML’s <input type=radio>
element.
This state can be manipulated by activating the element or other specified elements,
or by other user interactions.
2. Creating a Toggleable Element: the toggle-states and toggle-initial property
Name: | toggle-states |
---|---|
Value: | none | <integer> [cycle | sticky]? |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Media: | interactive |
Animatable: | no |
Name: | toggle-initial |
---|---|
Value: | <integer> |
Initial: | 0 |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Media: | interactive |
Animatable: | no |
The toggle-states property controls whether an element has toggleable state or not.
- none
- Indicates that the element does not have toggleable state.
- <integer> [cycle | sticky]?
- Indicates that the element is toggleable. The <integer> gives the number of states; it must be 2 or greater, or else the property is invalid. The following optional keyword defines the behavior when the element is already at its last state, and is toggled again: cycle defines that it should cycle back around to the first state, while sticky defines that it should stay at the last state.
If an element is toggleable, it has a toggle state, which is an integer from 0 to some maximum value, as defined by toggle-states. This state is incremented whenever the element is activated by the user, using the same defininition of "activated" as the :active pseudo-class, and can be selected by the :checked or :checked() pseudo-classes, as defined later in this specification.
<ul class='ingredients'> <li>1 banana <li>1 cup blueberries ... </ul> <style> li { toggle-states: 2; } li:checked { color: silver; text-decoration: line-through; } </style>
The effect is identical to what was specified in the Introduction example, except the markup is much simpler and more semantic.
The toggle-initial property sets the initial toggle state of the element. Its value must be a non-negative integer, or else the property is invalid. If the value is equal to or greater than the number of states defined by toggle-states, it computes to the greatest toggle state.
2.1. Linking Toggle States: the toggle-group property
Name: | toggle-group |
---|---|
Value: | none | <custom-ident> |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Media: | interactive |
Animatable: | no |
By default, each toggleable element’s toggle state is independent;
incrementing one has no effect an any other.
The toggle-group property allows elements to link their toggle states together
by declaring them to be part of a named toggle group,
such that only one can have a non-zero toggle state at a time,
similar to HTML’s <input type=radio>
element.
Each toggle group on a page is identified by a unique <custom-ident>.
- none
- The element is not in a toggle group. It’s toggle state is independent of any other elements'.
- <custom-ident>
-
The element is in the toggle group named by the <custom-ident>.
Any time an element in the given toggle group has its toggle state incremented,
the toggle state of every other element in the same toggle group is set to 0.
The keyword none is excluded from this <custom-ident>.
.tab { toggle-states: 2 sticky; toggle-group: "tabs"; toggle-share: select(attr(for idref)); } .panel { toggle-states: 2 sticky; toggle-group: "panels"; } .tab:checked { /* styling for the active tab */ } .panel:not(:checked) { display: none; }
Clicking on any tab will increment its toggle state from 0 to 1 (and the sticky keyword will keep it at 1 if activated multiple times), while resetting the rest of the tabs' toggle states to 0. The same happens to the panels, using the toggle-share property defined in a later section. The active tab and panel are styled and shown differently than the other tabs and panels.
The name is global to the page. Should we have a way to specify more implicit groups? Maybe a parent keyword, or a way to scope names to a subtree?
2.2. Sharing Toggle Activations: the toggle-share property
Name: | toggle-share |
---|---|
Value: | none | <selector-list> |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Media: | interactive |
Animatable: | no |
By default, activating an element only increments its own toggle state. The toggle-share property allows an element to additionally increment the toggle state of another element.
- none
- Activating the element only increments its own toggle state (assuming the element is toggleable).
- <selector-list>
-
Activating the element increments the toggle state of both itself and all the elements matched by the given selector
(assuming the elements are toggleable).
If the set of elements sharing the activation includes elements that share a toggle group, only the last such element (in document order) per group has its toggle state incremented.
Note: This functionality is similar in nature to HTML’s <label>
element,
but not identical. toggle-share causes the activation to be shared,
rather than transferred like <label>
does.
Additionally, this sharing only affects the toggle state,
while <label
more strongly transfers the concept of "activating",
affecting things such as click events and the :hover pseudo-class.
3. Selecting Elements Based on Toggle State: the :checked and :checked() pseudo-class
The :checked pseudo-class, defined in [SELECTORS-4], has its definition expanded to additionally match any element whose toggle state is non-zero.
The :checked(<integer>) pseudo-class selects any elements whose toggle state has the value given by its argument.
3.1. Problems with Combining :checked and toggle-* Properties
Naively combining :checked and the toggle-* properties causes circularity issues.
#foo { toggle-states: 2; toggle-initial: 1; } #foo:checked { toggle-states: none; }
To avoid this, the toggle-states and toggle-initial are defined as selector-affecting properties, and the :checked and :checked() pseudo-classes are defined as property-affected selectors. In any style rule whose selector includes a property-affected selector, any selector-affecting properties are invalid.
I’m not sure this is sufficient. For example, you could set the property in an animation, which is triggered by an affected selector. Is there a better way to define this so that it’s reliable?