| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Typique (pronounced /ti'pik/) is a framework- and bundler-agnostic, zero-runtime CSS-in-TS library powered by a TypeScript plugin. It generates readable, unique class and variable names directly as completion items in your editor. Styles exist only as types, so they vanish cleanly from your final build.
Completion demoimport type { Css } from 'typique'
import { space } from './my-const'
const titleClass = 'title-1' satisfies Css<{
paddingTop: `calc(2 * ${typeof space}px)`
'& > code': {
backgroundColor: '#eee'
}
}>Typique is built to feel boring — in a good way. No new runtime model, no clever indirections, just CSS generated directly from TypeScript with minimal friction.
Comparison of Typique and Vanilla Extract
A file type is supported if it is opened by the TypeScript server and contains TypeScript syntax.
Using workspace-scoped TypeScript plugins like Typique requires a workspace TypeScript. Both the typescript and typique packages must be installed in the same node_modules. To ensure this, run the npm i / pnpm add commands from the same directory — typically the project root:
npm i -D typescript
npm i typiqueOr:
pnpm add -D typescript
pnpm add typiqueIf you use VS Code, switch to the workspace TypeScript: Command Palette → Select TypeScript Version → Use Workspace Version.
{
"compilerOptions": {
"plugins": [
{
"name": "typique/ts-plugin"
}
]
}
}The path (or name) is resolved from the node_modules directory where typescript is installed, regardless of the relative location of tsconfig.json. Since typique is installed in the same node_modules, the path is always the same: typique/ts-plugin.
Note: specifying a path here is only supported starting with TypeScript 5.5; earlier versions support only a pathless plugin name.
Name your constants ...Class and ...Var to instruct Typique to suggest completion items in the constant initializers. In WebStorm, you may need to invoke explicit completion (Ctrl+Space) to see the suggestions.
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})`
}>Full naming conventions are explained further.
The suggested class names are guaranteed to be unique within the TypeScript project. The scope of this uniqueness is explained in more detail below.
By default, Typique outputs a single CSS file named typique-output.css in your project root. Import it into your HTML template or entry point:
<html>
<head>
...
<link href="./typique-output.css" rel="stylesheet">
</head>
...
</html>The file name is configurable.
Run the following command to generate the CSS file from the command line:
npx typique --projectFile ./index.ts --tsserver ./path/to/tsserver.js -- ...ts-argsUnlike tsc, tsserver.js does not allow explicitly specifying a custom tsconfig.json file. Instead, it locates the configuration internally when opening the file provided via --projectFile. This is usually the first tsconfig.json found when walking up the directory hierarchy from that file.
If you need to use a custom tsconfig.json, you can apply the following workaround:
One of the core ideas of Typique as a tooling is to recognize when you are about to specify a class or CSS variable name and suggest a readable, unique name via code completion. A location where Typique expects a class or CSS variable name is called a completion context. Completion behaves slightly differently depending on the context.
All of the examples above use this kind of context. Completion is suggested when the variable name matches a configurable pattern, which by default is:
This is useful for TSX-native frameworks such as React, Preact, SolidJS, Qwik, and others. Completion is shown in the value of a property whose name matches the configurable pattern, which by default is ^class(Name)?$:
export function Button() {
return <button className={ 'button' satisfies Css<{
// ^
// Completion appears here
border: 'none'
padding: `calc(${typeof unit}px * 2)`
}> }>
Click me
</button>
}In principle, you can manually add satisfies Css<...> or satisfies Var to any literal expression. This does not provide completion, but everything else — CSS generation and uniqueness checks — works the same way as in other contexts.
export function Button() {
return `<button class="${ 'button' satisfies Css<...> }" />`
}A context name is what Typique uses to generate class and css-var names. It is an internal identifier derived from the surrounding code.
const lgBtnClass = ''
// ^
// context name: lgBtnfunction AppTitle() {
return <h1 className={''} />
// ^
// context name: AppTitle/h1
}The context name is very close to the variable name or TSX path, yet not identical:
The context name defines which class/css-var names are suggested in this place. Actual names do not have to include the full context name. For example:
Finally, the naming options let you control how the context name is transformed into a class or css-var name. For example, you can add constant parts, random parts, or even exclude the context name entirely.
Typique validates that a name:
If a name is invalid, a diagnostic is shown:
Name-related diagnostics are always accompanied by appropriate quick fixes:
As a reminder, a TypeScript project consists of:
A single workspace can contain multiple TypeScript projects, which is common in monorepos.
Typique enforces name uniqueness at the TypeScript project level, not at the workspace level. To guarantee uniqueness across multiple TypeScript projects, you can add prefixes or suffixes via the naming options. For more advanced setups, see the Monorepos and Shared Code guide.
The next several sections describe the syntax Typique supports for defining styles. They are mostly independent and can be read in any order.
Nested rules are interpreted according to the emerging CSS Nesting Module specification. Currently, Typique downlevels nested rules into plain CSS; support for native CSS nesting is planned.
const fancyClass = 'fancy' satisfies Css<{
color: 'teal'
'@media (max-width: 600px)': {
color: 'cyan'
'&:active': {
color: 'magenta'
}
}
}>Output:
.fancy {
color: teal;
}
@media (max-width: 600px) {
.fancy {
color: cyan;
}
.fancy:active {
color: magenta;
}
}Allows defining multiple related CSS variables or class names in a single expression.
For CSS variables, make sure to add as const after the array initializer; otherwise, TypeScript will infer the type as string[]:
const [xVar, yVar] = ['--x', '--y'] as const satisfies Var
// ^
// A two-place completion appears hereFor styles, it’s possible to reference class names from the left-hand side using $0, $1, $2, and so on:
const [rootClass, largeClass, boldClass, smallClass] =
['root', 'large', 'bold', 'small'] satisfies Css<{
// ^
// A four-place completion appears here
padding: '1rem' // root
'&.$1': { // root.large
padding: '1.3rem'
'&.$2': { // root.large.bold
fontWeight: '700'
}
}
'&.$3': { // root.small
padding: '0.5rem'
'&.$2': { // root.small.bold
fontWeight: '600'
}
}
}>Typique validates that all names are referenced and that all $ references are valid.
$ references can be used to reference any identifier, not just class names. This is useful for keyframes, layers, and similar constructs.
const [buttonClass] = ['button', 'cn-1'] satisfies Css<{
// ^ ^
// Completion appears in both positions
animation: '$1 0.3s ease-in-out'
'@keyframes $1': {
from: {
opacity: 0
}
to: {
opacity: 1
}
}
}>The name 'cn-1' is derived from the configurable default context name and, like any other name, is guaranteed to be unique. If you need the generated keyframes name at runtime, you can request it on the left-hand side, as usual: const [buttonClass, fadeInKeyframes] = ....
For CSS variables, object notation is useful for defining themes:
const themeVars = {
bgColor: '--theme-bg-color',
// ^
// Completion appears here and likewise below
space: '--theme-space'
} as const satisfies VarAs with array notation, make sure to add as const after the object initializer; otherwise, TypeScript will infer the values as string.
For styles, this notation allows referencing class names via named $ references:
const buttonClasses = {
_: 'button',
// ^
// Completion appears here and likewise below
b: 'button-b',
sz: {
lg: 'button-sz-lg',
sm: 'button-sz-sm',
}
} satisfies Css<{
padding: '1rem' // associated with the first classname, 'button'
'&.$sz$lg': { // button.button-sz-lg
padding: '1.3rem'
'&.$b': { // button.button-sz-lg.button-b
fontWeight: '700'
}
}
'&.$sz$sm': { // button.button-sz-sm
padding: '0.5rem'
'&.$b': { // button.button-sz-sm.button-b
fontWeight: '600'
}
}
}>Object notation also enables selecting class names based on props in a declarative way using the co() function, explained below.
Styles that don’t contain non-object properties at the top level and don’t use $ references are emitted as-is, resulting in global CSS:
[] satisfies Css<{
body {
margin: 0
}
'.hidden': {
display: 'none'
}
'@font-face': {
fontFamily: 'Open Sans'
src: 'open-sans.woff'
}
}>Typique outputs this CSS unchanged. You can also mix local and global class names:
const flexClass = 'flex-0' satisfies Css<{
display: 'flex'
'&.hidden': {
display: 'none'
}
}>This outputs:
.flex-0 {
display: flex;
}
.flex-0.hidden {
display: none;
}Use tuple notation to assign multiple values to the same property.
const cClass = 'c' satisfies Css<{
color: ['magenta', 'oklch(0.7 0.35 328)']
}>Typique provides two utilities for combining class names: cc() and co(). Both are exported from the typique/util module.
Simply concatenates all values that are truthy. This is useful for selecting class names based on conditions, for example, prop values.
<button className={ cc(
'button' satisfies Css<{
border: 'unset'
}>,
isLarge && 'button-0' satisfies Css<{
fontSize: '1.4em'
}>,
) }/>Selects class names from a classesObject based on props. This utility is designed to work together with object notation.
<button className={ co(
{size},
{
_: 'button',
size: {
small: 'button-size-small',
large: 'button-size-large',
},
} satisfies Css<{
border: 'unset'
'.$size$small': {
fontSize: '.8em'
}
'.$size$large': {
fontSize: '1.4em'
}
}>,
) }/>Based on the value of the size variable passed in the first object, the resulting class name will be either button button-size-small or button button-size-large. See the co() TSDoc for more examples.
Here are some TypeScript recipes that come in handy with Typique.
Converts a constant to a literal type.
const unit = 4
const spacedClass = 'spaced' satisfies Css<{
padding: typeof unit // Type is 4, rendered as 4px
}>This works for both types and values.
const unit = 4
const padding = `${typeof unit}em` as const // Type is `4em`
type Padding = `${typeof unit}em` // Type is `4em`Note: the + operator produces the string and not a constant type. Make sure to always use the interpolation instead.
This is useful for assigning a value to a CSS variable.
const paddingVar = '--padding' satisfies Var
const spacedClass = 'spaced' satisfies Css<{
[paddingVar]: 4
}>This is example of the dark theme which is by default synchronized with the system theme, but also can be overridden by user settings.
import type {Css, Var} from 'typique'
const [bgColorVar, nameVar] = ['--bgColor', '--name'] as const satisfies Var
type Light<Name extends string = '🖥️'> = {
[bgColor]: '#fff'
[name]: `"${Name}"`
}
type Dark<Name extends string = '🖥️'> = {
[bgColor]: '#444'
[name]: `"${Name}"`
}
const [lightClass, darkClass] = ['light', 'dark'] satisfies Css<{
body: Light
'@media (prefers-color-scheme: dark)': {
body: Dark
}
'body.$0': Light<'☀️'>
'body.$1': Dark<'🌙'>
}>This can be used to join multiple type objects:
type NoPaddingNoMargin = {
padding: 0
margin: 0
}
const buttonClass = 'button' satisfies Css<NoPaddingNoMargin & {
// ...
}>Can be used, for example, to define specs of multiple properties at once:
[] satisfies Css<{
[_ in `@property ${typeof c0 | typeof c1}`]: {
syntax: '"<color>"'
initialValue: '#888'
inherits: false
}
}>Depending on community feedback, the project may evolve in the following directions:
Typique draws inspiration from the following great libraries:
| Back | FazBrowse Home | New Git URL |