PraxisJS

CSS

@praxisjs/css — scoped class names, reactive CSS custom properties, and a fluent builder. Zero runtime overhead in production with static extraction.

CSS

@praxisjs/css brings CSS into the PraxisJS decorator model. Scoped class names defined in typed classes, reactive CSS custom properties that update the DOM directly, and a fluent builder with full IDE autocomplete. Pair it with the Vite plugin for zero-overhead static extraction in production.

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

At a glance

import { keyframes, ReactiveStylesheet, Stylesheet, Styled, Param, Style, cx } from '@praxisjs/css'

// 1. Define styles with the fluent builder
class CardStyles extends ReactiveStylesheet {
  @Param() color  = '#6d5bbd'   // reactive CSS var → --color
  @Param() radius = '8px'       // reactive CSS var → --radius

  $root = this.css({ display: 'flex', gap: '12px', borderRadius: 'var(--radius)' })
    .hover({ boxShadow: '0 4px 12px rgba(0,0,0,0.1)' })

  $selected = this.css({ border: '2px solid var(--color)' })
  $title    = this.css({ fontSize: '1rem', fontWeight: 600, color: 'var(--color)' })
}

// 2. Use in a StatefulComponent
@Component()
class Card extends StatefulComponent {
  @State() selected = false

  @Styled(CardStyles) $card!: CardStyles  // injects scoped class names + reactive vars

  @Style('--surface') surface = '#fff'    // component-level CSS var

  render() {
    return (
      <div class={() => cx(this.$card.$root, { [this.$card.$selected]: this.selected })}>
        <h3 class={this.$card.$title}>Card</h3>
        <button onClick={() => { this.$card.color = '#ef4444' }}>Change color</button>
      </div>
    )
  }
}

Two base classes

ClassUse whenWorks with
StylesheetOnly $-prefixed CSS class fieldsAny component
ReactiveStylesheet$ fields + @Param() reactive varsStatefulComponent only

TypeScript enforces this — using @Styled(ReactiveStylesheetSubclass) on a StatelessComponent field is a compile-time error.


Static extraction (Vite plugin)

For production builds, praxisjsCSS() extracts all stylesheet rules at build time — no <style> tags are injected at runtime. CSS lives in a static asset with normal HTTP caching.

// vite.config.ts
import { praxisjs, praxisjsCSS } from '@praxisjs/vite-plugin'

export default defineConfig({
  plugins: [praxisjs(), praxisjsCSS()],
})

Then import the virtual module in your app entry:

// main.ts
import 'virtual:praxisjs/styles.css'   // includes all extracted CSS

Add the type declaration once (e.g. in vite-env.d.ts):

/// <reference types="vite/client" />
declare module 'virtual:praxisjs/styles.css' {}

How it works: during the Vite build, the plugin scans all source files for Stylesheet subclasses, keyframes() calls, globalStyle() calls, and @Themed() decorators. It bundles and evaluates them in a sandboxed Node.js context to compute the exact CSS output, then emits it as virtual:praxisjs/styles.css. In production, injectStyle becomes a no-op — the CSS is already in the bundle.

In development (Vite dev server), the virtual module is empty and CSS is injected at runtime as usual. HMR works without any extra configuration.

Reactive CSS vars (@Param(), @Style()) and theme switching (ThemeInstance.switch()) always update the DOM at runtime — the plugin only extracts static rules.


API overview

Stylesheets

ExportDescription
StylesheetBase class for static CSS definitions
ReactiveStylesheetBase class with @Param() support
@Styled(StyleClass)Field decorator — injects scoped class names
this.css({})Fluent builder with full csstype autocomplete
@Param()Reactive CSS var field inside a ReactiveStylesheet
@Style('--var')Reactive CSS var on the component element
keyframes()Scoped @keyframes animation
globalStyle(factory)Unscoped global CSS via fluent builder or raw string — resets, @font-face, base rules
preflight()Opinionated browser reset inspired by Tailwind CSS preflight
cx()Class name composition utility

Design tokens

ExportDescription
TokenSheetBase class for token skeletons and theme value classes
tokenVars(TokenClass)Typed CSS var reference accessor for use in Stylesheet fields
@Themed(skeleton, DefaultTheme)Class decorator — installs the token system on the root component
@Theme()Field decorator — injects the active ThemeInstance
theme()Imperative accessor for the singleton ThemeInstance

On this page