Skip to content

Instantly share code, notes, and snippets.

@roginfarrer
Last active June 23, 2023 08:43
Show Gist options
  • Save roginfarrer/8d2be13b51cd706f0c37df4459d68f4c to your computer and use it in GitHub Desktop.
Save roginfarrer/8d2be13b51cd706f0c37df4459d68f4c to your computer and use it in GitHub Desktop.
nested selectors for vanilla extract globalStyle

Vanilla Extract's globalStyle function is intended to style global selectors. One of its limitations is that it doesn't support SASS-like nested selectors. The selector passed to the first argument is as deep as the selector can go.

// ✅ Good!
globalStyle('.foo', {
  color: 'blue',
});

// ❌ No bueno
globalStyle('.foo', {
  color: 'blue',
  p: {
    color: 'red'
  }
});

This little function wraps the globalStyle utility to support nested selectors. It also supports referencing the parent selector with &, though it hasn't been thoroughly tested for more complex selectors.

nestedGlobalStyle('.foo', {
  color: 'blue',
  'li, p': {
    color: 'red',
    '&:hover': {
      color: 'yellow',
    }
  },
  '&::after': {
    margin: '23px'
  }
})

// .foo { color: blue; }
// .foo li, .foo p { color: red; }
// .foo li:hover, .foo p:hover { color: yellow; }
// .foo::after { margin: 23px; }
import { globalStyle, GlobalStyleRule } from "@vanilla-extract/css";
interface RecursiveGlobalStyle {
[k: string]: GlobalStyleRule | RecursiveGlobalStyle;
}
function globalUtil(selector: string, styles: RecursiveGlobalStyle) {
const write = (
key: string[],
value: RecursiveGlobalStyle | GlobalStyleRule
) => {
Object.entries(value).forEach(([nestedK, nestedV]) => {
if (typeof nestedV === "string" || typeof nestedV === "number") {
globalStyle(key.map((k) => handleAmpersand(selector, k)).join(", "), {
[nestedK]: nestedV,
});
} else {
write(
key.map((k) => handleAmpersand(k, nestedK)),
nestedV
);
}
});
};
Object.entries(styles).forEach(([key, value]) => {
write(
key.split(",").map((k) => k.trim()),
value
);
});
}
const handleAmpersand = (key: string, nestedKey: string): string => {
let finalSelector = nestedKey;
if (nestedKey.startsWith("&")) {
finalSelector = nestedKey.replace("&", key);
} else {
finalSelector = `${key} ${nestedKey.replace("&", key)}`;
}
return finalSelector;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment