PraxisJS

Reactive CSS

@Param() and @Style() — reactive CSS custom properties that update the DOM directly, without re-renders.

Reactive CSS

Two decorators let you bind signal values directly to CSS custom properties on DOM elements. Both update element.style directly — no class toggling, no re-render.


@Param() — vars inside a stylesheet

@Param() marks a field inside a ReactiveStylesheet subclass as a reactive CSS custom property. Setting the field on the injected stylesheet object calls element.style.setProperty('--fieldName', value) on the component's container element.

import { Param, ReactiveStylesheet, Styled } from '@praxisjs/css'
import { Component, State } from '@praxisjs/decorators'
import { StatefulComponent } from '@praxisjs/core'

class CardStyles extends ReactiveStylesheet {
  @Param() color  = '#3b82f6'   // → --color on the element
  @Param() radius = '8px'       // → --radius on the element

  $root  = this.css({ borderRadius: 'var(--radius)', border: '2px solid var(--color)' })
  $title = this.css({ color: 'var(--color)', fontWeight: 600 })
}

@Component()
class Card extends StatefulComponent {
  @Styled(CardStyles) $card!: CardStyles

  render() {
    return (
      <div class={this.$card.$root}>
        <h3 class={this.$card.$title}>Title</h3>
        <button onClick={() => { this.$card.color = '#ef4444' }}>Red</button>
        <button onClick={() => { this.$card.color = '#3b82f6' }}>Blue</button>
      </div>
    )
  }
}

Setting this.$card.color = '#ef4444' calls element.style.setProperty('--color', '#ef4444'). The class names in $root and $title stay the same — only the CSS var value changes.

Per-instance independence

Each component instance gets its own independent signal for each @Param() field. Two Card components on the same page have completely independent color and radius values.

Only inside ReactiveStylesheet

@Param() is constrained to ReactiveStylesheet subclasses at the TypeScript level. Using @Styled(ReactiveStylesheetSubclass) on a StatelessComponent field is also a compile-time error.

Storybook
Live demo — @Param() in action

@Style('--var') — vars on the component

@Style() is a field decorator that works like @State but also syncs the value to a CSS custom property directly on the component's container element.

The property name must start with --. Use it for component-wide theming variables that child elements consume via var(--name).

import { Style } from '@praxisjs/css'
import { Component } from '@praxisjs/decorators'
import { StatefulComponent } from '@praxisjs/core'

@Component()
class ThemeProvider extends StatefulComponent {
  @Style('--accent') accent = '#6d5bbd'
  @Style('--bg')     bg     = '#f8f7ff'
  @Style('--radius') radius = '8px'

  setTheme(accent: string, bg: string) {
    this.accent = accent
    this.bg     = bg
  }

  render() {
    return (
      <div style="padding: 20px; background: var(--bg);">
        <button
          style="background: var(--accent); border-radius: var(--radius);"
          onClick={() => this.setTheme('#ef4444', '#fef2f2')}
        >
          Red theme
        </button>
        {this.props.children}
      </div>
    )
  }
}

All child elements using var(--bg), var(--accent), or var(--radius) update instantly when setTheme() runs.

Cleanup on unmount

@Style removes the CSS property and stops the reactive effect when the component unmounts.

Storybook
Live demo — @Style()

@Param() vs @Style()

@Param()@Style()
Defined inReactiveStylesheet subclassComponent class
Accessed viathis.$card.colorthis.accent
CSS var--fieldName--fieldName
Component restrictionStatefulComponent only (via ReactiveStylesheet)Any component
Best forStyles tightly coupled to a @Styled classComponent-wide theming variables

Both set CSS custom properties on the container element — the difference is organizational.


Combining both

@Style() sets vars on the container; @Param() reads them in the class CSS. Together they separate theme control from class structure:

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

class AlertStyles extends ReactiveStylesheet {
  $root = this.css({
    padding: '16px', borderRadius: 'var(--radius)',
    borderLeft: '4px solid var(--border)',
    background: 'var(--bg)',
  })
}

@Component()
class Alert extends StatefulComponent {
  // @Style controls the container vars — shared by all nested elements
  @Style('--bg')     bg     = '#eff6ff'
  @Style('--border') border = '#3b82f6'
  @Style('--radius') radius = '8px'

  @Styled(AlertStyles) $alert!: AlertStyles

  setVariant(bg: string, border: string) {
    this.bg     = bg
    this.border = border
  }

  render() {
    return <div class={this.$alert.$root}>{this.props.children}</div>
  }
}
Storybook
Live demo — @Styled + @Style combined

On this page