Skip to content

Repository files navigation

jstyle

TypeScript-first CSS builder.

Features

  • Scoped Identifiers — Class names, CSS vars and animation names are isolated
  • Type-Safe Properties — All CSS properties are typed
  • Minified Identifiers — Generates short, unique identifiers (class names, vars, …)
  • Deterministic Builds — Minified identifiers are stored in a map file
  • Multi-Language Support — Outputs modules with constant identifiers for TypeScript and Rust
  • Class Maps — Efficient (interned strings) runtime state to class name mapping for conditional styles
  • CSS Minification — Built-in minification via Lightning CSS
  • Manifest File — Supports assetcraft manifest files (resolve urls and emit)

Installation

npm install jstyle
# or
bun add jstyle

Quick Start

import{ns,q,style,emit}from'jstyle';import*aspfrom'jstyle/props';constAPP=ns('app');constBUTTON=APP.class('button');construles=[style(q.class(BUTTON),[p.display('inline-flex'),p.padding('8px 16px'),p.backgroundColor('blue'),]),style(q.class(BUTTON).hover,[p.backgroundColor('darkblue')]),];awaitemit({input: [{name: 'app',build: async()=>({css: rules})}],outDir: './dist',renderURL: (name,hash)=>`/assets/${name}.${hash}.css`,});

Imports

import{ns,q,style,media,$,env,important}from'jstyle';// Core types and factoriesimport*aspfrom'jstyle/props';// CSS property constructorsimport{emit}from'jstyle/emit';// Emit orchestratorimport{JSEmitter}from'jstyle/emit/js';// JS emitterimport{RustEmitter}from'jstyle/emit/rust';// Rust emitter

Core Concepts

Namespaces

Each module gets its own namespace to avoid identifier collisions:

import{ns}from'jstyle';constSCAFFOLD=ns('app.scaffold');constCONTAINER=SCAFFOLD.class('container');

Properties

Typed constructors from jstyle/props:

import*aspfrom'jstyle/props';p.display('grid');p.margin('0 auto');p.color('#333');// Predefined constantsp.FLEX;// display: flexp.BORDER_BOX;// box-sizing: border-box

Value Helpers

// CSS variablesconstSPACING=NS.dashedIdent('spacing');style('section',[p.padding($(SPACING))]);// padding: var(--spacing);// Environment variablesstyle('input',[p.padding(env('safe-area-inset-top'))]);// !importantstyle('modal',[important(p.zIndex('9999'))]);// z-index: 9999 !important;

Selectors

Create selectors using the q factory:

constBTN=q.class('button');// .buttonconstTITLE=q.tag('h1');// h1constLINK=q.id('nav-link');// #nav-linkconstSELF=q.self;// &constANY=q.universal;// *

Pseudo-classes and pseudo-elements via getter properties:

BTN.hover;// .button:hoverBTN.before;// .button::beforeBTN.nthChild(2,1);// .button:nth-child(2n+1)BTN.has(q.class('red'));// .button:has(.red)

Combinators for complex selectors:

q.class('card').descendant(q.class('card_title'));// .card .card_titleq.class('card').child(q.class('card_title'));// .card > .card_title

Functional pseudo-classes with selector arguments:

q.class('button').is([q.class('a'),q.class('b')]);// .button:is(.a, .b)q.class('button').not(q.class('link'));// .button:not(.link)q.class('button').where(q.class('link'));// .button:where(.link)

At-Rules

Factory functions for all CSS at-rules:

import{media,container,keyframes,layer,supports,fontFace}from'jstyle';media('(min-width: 768px)',[style(container,[p.margin('0')])]);keyframes('fade',[pct(0,[p.opacity('0')]),pct(100,[p.opacity('1')])]);layer('utilities',[style(highlight,[p.backgroundColor('yellow')])]);fontFace([p.fontFamily('MyFont'),p.src('url(font.woff2)')]);

Size and Color Utilities

import{Size,rgba}from'jstyle';Size.px(16);// 16pxSize.rem(2);// 2remrgba(255,0,0,1);// rgba(255, 0, 0, 1)

Class Maps

Efficient runtime state to class name mapping:

constbuttonMap=NS.classMap({base: BUTTON,states: {hovered: BUTTON_HOVER,active: BUTTON_ACTIVE,size: [null,BUTTON_SM,BUTTON_MD,BUTTON_LG],},exclude: (state)=>state.get('hovered')&&state.get('active'),});

Emitting

import{emit}from'jstyle/emit';import{JSEmitter}from'jstyle/emit/js';import{RustEmitter}from'jstyle/emit/rust';awaitemit({input: [{name: 'app',build: async(ctx)=>({css: [/* your rules */],}),},],outDir: './dist',renderURL: (name,sha)=>`/assets/${name}.${sha}.css`,emit: [newJSEmitter({outDir: './packages/css/src',clean: true}),// optional: emit JS modulesnewRustEmitter({outDir: './crates/css',clean: true}),// optional: emit Rust module],minify: true,// optional: minify CSSmap: './dist/.cssmap.json',// optional: persist ID mappings});

Output Formats

  • CSS — Always emitted
  • JS — Via JSEmitter from jstyle/emit/js: identifier bindings + TypeScript declarations
  • Rust — Via RustEmitter from jstyle/emit/rust: identifier bindings
  • Custom — Implement the Emitter interface from jstyle/emit/emitter for custom output formats

License

MIT OR Apache-2.0

About

TypeScript-first CSS builder

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages