It all started with React Server Components (RSC). Before they entered the picture, most CSS-in-JS libraries lived peaceful lives: once you wired them into a framework, things mostly just worked. RSC changed that landscape by drawing a hard, non-negotiable boundary between client and server components — a line that only zero-runtime solutions were allowed to cross. This should have been their golden age, right?
It wasn’t.
Instead, it became clear that “supporting every major bundler” means chasing a target that never stands still. Despite extraordinary dedication from maintainers, projects like Vanilla Extract and Linaria are overwhelmed by integration and compatibility issues. The most sobering moment for me was seeing Pigment CSS — a project many believed in, backed by MUI’s expertise — being paused for exactly the same reasons.
Digging deeper didn’t bring relief; it only exposed deeper layers of the problem. Over time, it started to feel like this wasn’t something that could be fixed by integrating more tightly with bundlers. The solution had to come from a different direction — something fundamentally orthogonal to bundlers themselves.
What bundlers already understand
Every modern frontend toolchain already has support for one thing:
- It works everywhere, across bundlers and build pipelines.
- It magically vanishes from the output without effort.
- It requires no extra parsing or transformation.
That thing is TypeScript types.
This observation is the foundation of Typique. Styles live in the type system, while only what truly belongs at runtime — class names and CSS variable names — exists as plain constants. By the time your code reaches a bundler, everything is already in its final, ordinary TypeScript form. Removing types is not Typique’s responsibility; bundlers have been doing that part reliably for years.
What Typique is
Typique is not a conventional library that processes your code after it’s written. Instead, it works as tooling that collaborates with you while you write styling code, guiding you toward correct CSS and unique names. Technically, it’s a TypeScript plugin that does two things:
- It integrates with your IDE to provide completion, validation, and quick fixes for class and CSS variable names.
- It uses data TypeScript has already parsed and evaluated to read types and emit plain CSS.
This is what sets Typique apart from previous approaches. There are no bundler plugins, no framework hooks, and no fragile integration layers — just TypeScript and an IDE with a proper language service support, such as VS Code or WebStorm. Everything else follows naturally from that.
The workflow
After setup, it usually goes like this. You create a variable that follows the naming conventions, and Typique steps in: via auto-completion it suggests a unique name and inserts the necessary syntax to start writing your CSS:
Next, you define the CSS itself, which may end up looking like this:
import type { Css } from 'typique'
import { space } from './my-const'
const titleClass = 'title-1' satisfies Css<{
paddingTop: `calc(2 * ${typeof space}px)`
'& > code': {
backgroundColor: '#eee'
}
}>
By the moment you finish typing it, the CSS file is already generated. You can immediately include it in your HTML template, entry point, or root component:
<html>
<head>
...
<link href="./typique-output.css" rel="stylesheet">
</head>
...
</html>
Validation
If a classname doesn’t match its context or collides with an existing one, Typique underlines it and suggests a fix:
Less boilerplate in TSX
In TSX, you don’t even need to introduce a separate constant. Classnames can be written directly in class / className props, with the same completion and validation:
export function Button() {
return <button className={ 'button' satisfies Css<{
// ^
// Completion appears here
border: 'none'
padding: `calc(${typeof space}px * 2)`
}> }>
Click me
</button>
}
CSS vars
CSS variables work the same way as classes: you declare a variable following the conventions, get a suggested name via completion, and then reuse it both in styles and at runtime:
import type { Css, Var } from 'typique'
const sizeVar = '--size' satisfies Var
// ^
// Completion appears here
const roundButtonClass = 'round-button' satisfies Css<{
// ^
// Completion appears here
[sizeVar]: 32
borderRadius: `calc(${typeof sizeVar} / 2)`
height: `var(${typeof sizeVar})`
width: `var(${typeof sizeVar})`
}>
What’s more?
There are many small features that smooth out the workflow: array and object notation, fallbacks, command-line generation, and more. See the full documentation in the README.
Tradeoffs
Solving hard problems usually means introducing new constraints at a different level. In practice, it’s a matter of believing that the new problems are smaller than the old ones — and in this case, I do.
Editor-centric
Typique works well in VS Code and mostly well in WebStorm. In theory, it should work in any IDE that relies on the TypeScript language service, since that’s exactly where Typique plugs in. In practice, less common IDEs may have corner cases or rough edges.
TypeScript-aligned
Because name uniqueness is scoped to a TypeScript project, using Typique requires a basic understanding of what a TypeScript project actually is (here’s a short reminder), and how a workspace may be split into multiple TS projects. Files that belong to an ambiguous project can cause issues — though there are well-established ways to handle this.
Explicit syntax
To stay bundler-independent, Typique needs three things to exist explicitly in your code:
- the styles,
- the class or CSS variable name,
- and the syntactic glue that connects them.
Most previous CSS-in-JS/TS tools only require the first one, so Typique’s syntax can look verbose by comparison. It is more explicit — but that explicitness is intentional, and not necessarily final. I believe that there’s room for refinement here, especially with community feedback.
AI-friendliness
Typique plays well with AI-assisted completion tools such as Copilot or Windsurf. If they guess a class or CSS variable name incorrectly, fixing it is trivial — Quick Fixes handle that immediately.
With agent-generated code, things look different. When an agent touches dozens of files at once, applying Quick Fixes manually doesn’t scale. The obvious next step here is a CLI tool that scans the project and fixes all invalid names in one pass — something agents could run periodically to clean things up.
Final thoughts
I didn’t expect Typique to turn into this, to be honest. What started as a thought experiment ended up as a full, production-ready tool — with real constraints, real tradeoffs, and real users in mind.
That said, it’s still early. The core is here, the hard parts are solved, but how this project evolves should be shaped by feedback, not guesses. If you’re curious, skeptical, or excited — all of that is useful. Some possible next steps are outlined in the Plans section, but none of them are set in stone.
If you’re thinking about using Typique in your project and want to talk it through, I’m very open to that. Early adopters tend to influence projects the most — and I’d like this one to grow in the right direction.
Thanks for reading — and for making it this far.


Top comments (0)