Custom State
In CSS, we have the ability to target native states of various elements using the pseudo-class
syntax (for instance, :hover
or :active
).
While this is very handy, as of today, CSS does not provide a way for us to define a custom state on every element. For example, we might want a registered
custom state to represent a player element that has signed up for our game.
We could choose to implement such a state manually by using a class selector (e.g. using a BEM modifier: .NAMESPACE--registered
) or attribute selector (e.g. [data-NAMESPACE-registered]
).
This is a bit of a pain for several reasons. Unlike native pseudo-classes, a class or an attribute selector is not semantic, and so we lose the original intent. If a convention like BEM is used, then the semantics are conserved, but it increases the verbosity of our code.
By defining our states through Stylable, we gain the benefit of validations, completions, and a consistent syntax for states.
Define and target
To define a registered state for our player
class, we will use the -st-states
declaration, and pass it a single state name of registered
.
We can then target this state using our newly-defined :registered
pseudo-class, knowing it will be transformed to a valid, safe selector at build time.
.player {
-st-states: registered; /* definition */
}
.player:registered {} /* usage */
/* OUTPUT - namespace class and state */
.game__player {}
.game__player.game--registered {}
In the example above, we defined a registered
boolean state that can either be targeted, or not. Let's imagine, though, that in addition to the registered
state, we now want to highlight first
, second
, and third
ranking players.
We can define such a state by passing the enum
parameter type to the ranking
state definition, and in it, list our possible options.
.player {
-st-states: registered,
ranking(enum(first, second, third));
}
.player:ranking(first) {}
.player:ranking(second) {}
.player:ranking(third) {}
There are additional parameter types, as well as validators, default values, and mapping capabilities to Stylable custom states. Read more in the API reference.
At this point, you may have noticed that we can define states that would conflict with native CSS ones. For example, we can define a "custom" state called :hover
that would conflict with the native :hover
pseudo-class.
.player {
-st-states: hover;
}
.player:hover {} /* custom state, not the native one */
Due to this potentially confusing behavior, we strongly recommend against naming states that would conflict with native ones.
Runtime toggle
Now that we have defined our states, we want to be able to toggle them at runtime in our component.
import { cssStates, classes } from "./game.st.css";
// create a registered first ranking player
<div className={
classes.player + " " + cssStates({
registered: true,
ranking: "first"
})}>
</div>;
Looking at the example above, we import the cssStates
function and the classes
mapping from our game.st.css
stylesheet. We then create a div
element to represent a registered and first rank player.
This example, where we manually concatenate the class and states, is quite verbose and potentially confusing. In the next chapter, we will explore Stylable's runtime feature set and see a cleaner way of working with states at runtime.