Skip to main content

Import Class

In the Runtime chapter, we saw how a Stylable stylesheet's API can be imported into a JavaScript module, and how that API can be used. In this chapter, we'll see how our game example can be divided into linked stylesheet modules to provide a better development experience.

Up to this point, we've talked mostly about a single stylesheet for our project (game.st.css). Now we'll explore how to organize our code across multiple stylesheets.

Current status:

game.st.css
.board { /* ... */ }

.player {
-st-states: registered,
ranking(enum(first, second, third));
background: blue;
}

Stylesheet root

Let's refactor the player class from out of our game.st.css to be in its own stylesheet.

player.st.css
.root {
-st-states: registered,
ranking(enum(first, second, third));
background: blue;
}

We created a new player.st.css file above, and in it, created a new .root class. Then we moved the contents of the .player class rules to our new root class.

default root class

In Stylable, each component stylesheet is represented by a root class that is automatically created, even if not explicitly defined by the user. This root class is expected to be applied to the root element of the component.

We can see that when we moved the player class, we also included its styling declarations. This might cause additional work in the future when trying to customize this class from the outside.

base style recommendation

Stylable recommends that a component's stylesheet include minimal styling - only what is crucial for the functionality of the component or defines its structure. To follow this best practice, we need a way to import the player.st.css stylesheet over to game.st.css, and perform our styling customizations there.

Default import

Keep in mind that, as we saw earlier in the Namespacing chapter, classes in Stylable are automatically namespaced. This means that if we wish to target a class from another stylesheet in a selector, we need to import it to link to it.

Below, we import the player.st.css stylesheet and customize it by using the @st-import at-rule, and providing it with a local name for the default export (beginning with a capital letter) and a request to the target stylesheet. Then we move any customizations out of player.st.css into game.st.css.

game.st.css - moved player customization
@st-import Player from "./player.st.css";

.board { /* ... */ }

Player {
background: blue; /* moved from player.st.css */
}

Namespacing output

game.st.css
@st-import Player from "./player.st.css";  

/* namespaced locally */
.game__board { /* ... */ }

/* namespaced by player.st.css */
.player__root {
background: blue;
}

Stylesheet root selector

It's important to note that a default import of a Stylable stylesheet represents a component root, and is in fact pointing to the root class of that stylesheet.

Unscoped warning

By importing the default export of a stylesheet and then styling it, we are potentially customizing every instance of the component across our project. It is therefore strongly recommended to scope any selector that originates from a different namespace (stylesheet) or includes native elements with a local class.

@st-import Player from "./player.st.css";

/* report unscoped warnings */
Player {}
div {}

/* no warnings */
.root Player {}
.root div {}

Class named import

For the next step, we would like to add avatar and username parts to player.st.css. These will be applied to the inner elements of the component, and are assumed to be nested under the root class.

player.st.css - added avatar and username
.root {
-st-states: registered,
ranking(enum(first, second, third));
}
.avatar {}
.username {}

We then proceed to bind these new classes to their matching elements in the component.

player.jsx - bind avatar and username
import { st, classes } from "./player.st.css";

const Player = ({ className }) => {
return (
<div className={st(classes.root, { /* states */ }, className)}>
<image className={classes.avatar} />
<span className={classes.username}></span>
</div>
)
}

Now that we have these new classes, we can customize them externally through game.st.css. We do this by adding two named imports, inside square brackets, that link to our classes.

game.st.css - player customization
@st-import Player, [avatar, username] from "./player.st.css";

Player {
/* paint the Player component background blue */
background: blue;
}
Player .avatar {
/* circle avatar */
border-radius: 50%;
}
Player .username {
/* all caps username */
text-transform: uppercase;
}

Namespacing output - selectors only

game.st.css
@st-import Player, [avatar, username] from "./player.st.css";  

.player__root {}
.player__root .player__avatar {}
.player__root .player__username {}

Validations

One of the nice things that we gain by using these Stylable imports is that requested symbols are validated, and we'll be notified if any of the imported parts are not found.

game.st.css - missing import
/* report an error about an unknown "missingImport" symbol */
@st-import [missingImport] from "./player.st.css";

Exported by default

Any classes and other symbols defined in a stylesheet are automatically exported and available for import.

Explicit exports

We are in the process of developing explicit export support, and will expand this section once available.

Further import use-cases

Up to this point, we've only needed to import classes between stylesheets. In later chapters of this guide, we will learn how to import and use additional symbols, as well as additional import use cases from JavaScript to our stylesheet.

Check out the API cheatsheet to see various @st-import examples.


In this chapter, we've seen how to divide a stylesheet to separate modules, and how to customize those externally. We approached doing this by manually importing each class (default or named), and customizing them together using a selector.

In the Next chapter, we'll learn how Stylable can extend stylesheets, exposing their inner parts as an API to make our lives easier.