How to style child elements with vanilla extract?

9.9k Views Asked by At

I am starting to use vanilla extract to style a NextJS app. Is there any way to style child elements from the parent without creating another class?

I have an structure like this in the react component:

 <ul className={styles.ul}>
   <li>a</li>
   <li>b</li>
   <li>c</li>
 </ul>

And something like this in the style file:

import { style } from "@vanilla-extract/css"

export const ul = style({
    display: vars.display.flex,
    listStyleType: vars.none,
})
2

There are 2 best solutions below

7
On BEST ANSWER

You can use & notation with help of css selector like nth-child in your style as i show below:

Edit: This snippet code wont work at all :(

  export const ul = style({
    display: vars.display.flex,
    listStyleType: vars.none,
    selectors:{
      '& li:nth-child(n)': {
          display: vars.display.flex,
      }
    }
  })

In fact, & represent your style like you may know in css. .class-name:nth-child. & means class-name in this example.

Edit: As vanilla extract documentation noted: To improve maintainability, each style block can only target a single element. To enforce this, all selectors must target the “&” character which is a reference to the current element.

For example, '&:hover:not(:active)' and [${parentClass} &] are considered valid, while '& a[href]' and [& ${childClass}] are not.

If you want to target another scoped class then it should be defined within the style block of that class instead.

For example, [& ${childClass}] is invalid since it doesn’t target “&”, so it should instead be defined in the style block for childClass.

If you want to globally target child nodes within the current element (e.g. '& a[href]'), you should use globalStyle instead.

useful links:
https://github.com/seek-oss/vanilla-extract
https://tsh.io/blog/vanilla-extract-library/

0
On

You do not need to create a new CSS class to target li elements, but you do need to have two different CSS rules, whatever the preprocessor you use or in plain CSS.

Keep in mind what you would write in plain CSS:

.some-specific-ul {
    margin: 4em;
}

.some-specific-ul > li {
    color: grey;
}

What you'd write using vanilla-extract is almost the same. The only difference is that the class name is not hardcoded anymore, but generated somehow. Still, you can use the class name in multiple rules.

import { globalStyle, style } from "@vanilla-extract/css";

export const ul = style({
    margin: '4em',
});

globalStyle(`${ul} > li`, {
    color: 'grey',
});

In both cases, you only need one class to apply to the ul element, and li elements would be automatically styled as well. In the second case, only the ul class name is exported, and that makes sense: there's no way to force the li rule, it can only be applied automatically depending on the parent ul.

Now, keep also in mind that your code may become less maintainable if you abuse of "magically applied rules". As the number of rules and the element hierarchy depth grow, people won't know where the styles are coming from. I believe there's a point beyond which it's worth to explicit the styles you apply. Under the hood it creates CSS classes, but you shouldn't care about that.

Imagine:

import { style } from "@vanilla-extract/css";

export const ul = style({
    margin: '4em',
});

export const li = style({
    color: 'grey',
});
import * as React from "react";
import * as classNames from "./my-list.css";

export const MyList: React.FC<{ items: string[] }> = ({ items }) => (
    <ul className={classNames.ul}>
        {items.map(item => (
            <li key={item} className={classNames.li}>{item}</li>
        )}
    </ul>
);

The take-away advice is that your style file will look the same, you'll loose a little time to write additional class names, but you'll gain a lot more time in maintenance.