Skip to content

Instantly share code, notes, and snippets.

@jacobrask
Last active October 15, 2023 14:43
Show Gist options
  • Save jacobrask/9f84754f6c2a3f31639c940fd1dc110e to your computer and use it in GitHub Desktop.
Save jacobrask/9f84754f6c2a3f31639c940fd1dc110e to your computer and use it in GitHub Desktop.
CSS variable TypeScript proxy
/**
* PoC using recursive proxies to maintain a typed interface for your CSS variable themes.
*
* See https://www.joshwcomeau.com/css/css-variables-for-react-devs/ for an excellent intro to CSS variables.
*
* Using CSS variables as plain strings in TypeScript is not great though.
* It's prone to typos, difficult to refactor and you can't easily deprecate properties or add aliases.
* One solution is to create a JavaScript object of your theme, with the CSS variable as the value, but
* if users are already downloading your theme as CSS variables, why make them download it again as a JS object?
*/
// The theme interface can be written manually or generated by a tool like
// Style Dictionary that can generate both your CSS and theme interface from style tokens
interface Theme {
color: {
foreground: {
primary: string;
secondary: string;
action: string;
};
background: {
primary: string;
}
};
font: {
size1: string;
size2: string;
}
};
let empty = Object.create(null);
function createProxy(prefixes: string[]): Theme {
return new Proxy(empty, {
get(_target, key: string) {
if (typeof key === 'symbol' || key === 'toString') {
return () => `var(--${prefixes.join('-')})`;
} else {
return createProxy([...prefixes, key]);
}
},
});
}
const theme = createProxy([]);
console.assert(theme.font.size1.toString() === 'var(--font-size1)');
console.assert(theme.color.foreground.primary.toString() === 'var(--color-foreground-primary)');
.theme {
--color-foreground-primary: #111;
--color-foreground-secondary: #6B818C;
--color-foreground-action: #08415C;
--color-background-primary: #fff;
--font-size1: 3rem;
--font-size2: 2rem;
}
@nsaunders
Copy link

I really like this--thanks for sharing! For my own purposes (since I don't use Style Dictionary), I think I might define the theme as a value and let TypeScript infer the type for me. That way, I could use it to generate the CSS as well as for type information.

e.g.

const defaultTheme = {
  color: {
    foreground: {
      primary: "blue",
      secondary: "black",
      action: "green",
    },
    background: {
      primary: "black",
    },
  },
  font: {
    size1: "1rem",
    size2: "0.875rem",
  },
};

type Theme = typeof defaultTheme;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment