Skip to main content


In addition to CSS's native pseudo-classes, like :hover and :nth-child(), Stylable allow you to define custom pseudo-classes so that you can apply styles to your components based on state. Let's say you want a component to have different styling applied to it when its content is loading. You can define loading as a custom pseudo-class and toggle it in your component.

This page goes over how Stylable handles custom pseudo-classes, for more details about the language feature itself, checkout the following resources:


The Stylable -st-states declaration is used on a class selector to provide a list of the possible custom pseudo-classes that can later be targeted from CSS and controlled from JavaScript.

list of custom states
.x {
-st-states: stateA, stateB;
allowed context

-st-states is only allowed in a rule with a single class selector to indicate custom pseudo classes for that class.


To define a custom state that can be either on or off, provide state definitions with a single ident as the name for the state, then target the state using a pseudo-class.

.x {
/* define 2 boolean states on 'x' */
-st-states: toggled, loading;

/* target single custom state */
.x:toggled {}

/* target multiple custom states */
.x:toggled:loading {}


A custom state with a restricted option list that can target one of the options using a pseudo-class selector with a matching parameter and an optional default value.

.x {
/* define a 'size' custom state with 3 options
and a 'color' state with options and a default 'green' value */
-st-states: size(enum(small, medium, large)),
color(enum(reg, green, blue)) green;

/* target an element with a size of `medium` */
.x:size(medium) {}

/* INVALID! - 'huge' is not a possible value for size */
.x:size(huge) {}

/* INVALID! - parameter is required for size */
.x:size {}

/* VALID! - same as ':color(green)' */
.x:color {}


A custom state that can accept a string parameter value.

.x {
/* define 'category' custom state that accepts a string parameter */
-st-states: category(string);

/* target an element with state category='kitchen' */
.x:category(kitchen) {}

/* target an element with state category='office' */
.x:category(office) {}

Optional string validation

There are several options that can be used to validate the potential string parameter input:

  • minLength - validates min length of input
  • maxLength - validates max length of input
  • contains - validates substring value exist in input
  • regex - validates input against a regular expression

To set validations on a string state type, call the string as a function and pass a list of validations.

string validation definition examples
.a { 
/* validates input has min/max length and has the string 'user' in it */
-st-states: x(string(minLength(5), maxLength(10), contains('user')));

.b {
/* validates input begins with 'user' */
-st-states: x(string(regex('^user')));


A custom state that can accept a number parameter value.

.a {
/* define 'ranking' custom state that accepts a number parameter */
-st-states: ranking(number);

/* target an element with a state rankin='5' */
.a:ranking(5) {}

Optional number validation

There are several options that can be used to validate the potential number parameter input:

  • min - validates min size of input
  • max - validates max size of input
  • multiplyOf - validates input is multiply of a given value
number validation definition example
.a {
/* validates the targeting number argument */
-st-states: x(number(min(2), max(6), multipleOf(2)));

/* valid arguments */
.a:x(2) {}
.a:x(4) {}
.a:x(6) {}

/* INVALID! - "multipleOf(2)" */
.a:x(3) {}
.a:x(5) {}

/* INVALID! - "min(2)" and "max(6)" */
.a:x(1) {}
.a:x(7) {}

State inheritance

When using -st-extends to extend another stylesheet or class, states definitions are inherited, but can also be overridden at any level.
.root {
/* define states 'a' and 'b' for the root of base stylesheet */
-st-states: a, b;
@st-import Base from './';

.root {
/* inherits states 'a' and 'b' */
-st-extends: Base;

/* define states 'c' and 'b' (override 'b' from 'Base') */
-st-states: c, b;

.root:a {}
.root:b {}
.root:c {}

/* OUTPUT */
.extend__root.base--a {} /* 'a' defined in base */
.extend__root.extend--b {} /* 'b' defined in base, but overridden in extend */
.extend__root.extend--c {} /* 'c' defined in extend */
Native pseudo-class override

You can override the behavior of native pseudo-classes. This can enable you to write polyfills for forthcoming CSS pseudo-classes to ensure that when you define a name for a custom pseudo-class, if there are clashes with a new CSS pseudo-class in the future, your app's behavior does not change. We don't recommend you to override an existing CSS pseudo-class unless you want to drive your teammates insane.


To activate a custom state, use the cssStates or st() function to generate the active CSS classes states.

import { st, classes } from './'; 

// active states
st(classes.part, {
isOn: true, // boolean
size: 'small' // string or enum
place: 1 // number state

// un-active states - only 'part' class
st(classes.part, {
isFirst: false, // boolean
size: undefined // string or enum
place: undefined // number


Stylable generates namespaced CSS classes for custom states:

.x:bool {}
.x:enum(option1) {}
.x:string(word) {}
.x:number(55) {}

/* OUTPUT */
.NAMESPACE__x.NAMESPACE---enum-7-option1 {}
.NAMESPACE__x.NAMESPACE---string-4-word {}
.NAMESPACE__x.NAMESPACE---number-2-55 {}

Map to selector

In some cases the default way Stylable transforms a state into a class is not the desired behavior, for example when writing style interface to an external view that might mark state as an attribute, For this mapped state can be used to transform the state into a another selector.

To define a global mapped state, pass a selector as a string instead of a type:

.x {
-st-states: toggled('.on'),

.x:toggled {}
.x:loading {}

/* OUTPUT */
.NAMESPACE__x.on {}
.NAMESPACE__x[dataSpinner] {}

Additionally a single parameter can be defined after the selector string and referenced in the selector string using $0:

.x {
-st-states: size('[size="$0"]', enum(small, medium, large));

.x:size(small) {}

/* OUTPUT */
.NAMESPACE__x[size='small'] {}