Using vanilla-extract, how am I expected to define styles for nested elements?

561 Views Asked by At

Mantine v7 just moved over to CSS modules and officially recommends vanilla-extract for those wanting to continue using CSS in JS. I'm working on porting a project over, and I feel like I must be missing something. Apparently just nesting CSS selectors like in emotion or postCSS or Sass or Less results in a typescript error, even though it compiles properly.

export const listContainer = style({
  ul: {
    li: {
      listStyle: 'none',
    }
  }
})

Object literal may only specify known properties, and 'ul' does not exist in type 'ComplexStyleRule'.ts(2353)

But this produces valid CSS and does what you would expect it to do.

Reading the docs, it's clear that we're expected to define flat CSS rules, which feels to me like a regression back to plain old CSS files. Half the reason to use preprocessors is for nesting selectors.

Here's an example from me trying to port existing styles from emotion over to vanilla-extract for a navbar:

export const navbar = style({
  transition: 'width 100ms ease-in-out, min-width 100ms ease-in-out',

  'ul li': {
    position: 'relative',
    borderLeftWidth: `${borderWidth}px`,
    borderStyle: 'solid',
    borderLeftColor: 'transparent',

    '&.active': {
      borderLeftColor: 'orange',
    },

    '&:hover': {
      borderLeftColor: 'purple',
    },
  },

})

Ok, we can't do that because nesting (even though it produces the correct CSS), so I'll pull it out into a globalStyle:


export const navbar = style({
  transition: 'width 100ms ease-in-out, min-width 100ms ease-in-out',
})

globalStyle(`${navbar} ul li`, {
  // ...

  '&.active': { /* ... */},

  '&:hover': { /* ... */},
})

Which, again, actually does work, but complains about &.active not existing in type 'GlobalStyleRule'. Ok, but I can pull it out as its own style variable and it won't complain anymore:

export const navbar = style({ /* ... */ })

export const active = style({})

globalStyle(`${navbar} ul li`, {
  // ...
  [`&.${active}`]: { /* ... */},

  '&:hover': { /* ... */},
})

But now it turns out you can't use selectors in globalStyle. So I'll need to pull that out into its own declaration...

globalStyle(`${navbar} ul li:hover`, { /* ... */ })

In my opinion, that first version is so much more readable and maintainable, and it even produces the expected CSS. I feel like I must be configuring it wrong to be getting these errors while the syntax I'm using actually does what I expect it to do. Why would it properly handle nested selectors while simultaneously complaining through the type system?

So I guess my question is, am I understanding how to use this library and just not liking it, or have I fundamentally misunderstood something?

0

There are 0 best solutions below