Stylesheets
Stylesheet and ReactiveStylesheet — base classes for defining scoped CSS. @Styled injects class names into components.
Stylesheets
Styles are defined by extending Stylesheet (or ReactiveStylesheet) and declaring $-prefixed fields. Each field becomes a scoped class name when injected into a component via @Styled.
Stylesheet
Use when you only need static class names — no reactive CSS vars. Works on any component, including StatelessComponent.
import { Stylesheet } from '@praxisjs/css'
class BadgeStyles extends Stylesheet {
$root = this.css({ display: 'inline-flex', padding: '2px 10px', borderRadius: '99px' })
$success = this.css({ background: '#f0fdf4', color: '#15803d' })
$error = this.css({ background: '#fef2f2', color: '#b91c1c' })
}Plain template strings are also accepted:
class LabelStyles extends Stylesheet {
$root = `font-size: 0.875rem; font-weight: 500; color: #374151;`
}ReactiveStylesheet
Use when you need @Param() reactive CSS vars alongside class fields. See Reactive CSS for the full @Param() reference.
import { ReactiveStylesheet, Param } from '@praxisjs/css'
class CardStyles extends ReactiveStylesheet {
@Param() color = '#3b82f6'
@Param() radius = '8px'
$root = this.css({ borderRadius: 'var(--radius)', border: '2px solid var(--color)' })
}@Styled(ReactiveStylesheetSubclass) can only be applied to StatefulComponent fields — TypeScript enforces this at compile time.
@Styled(StyleClass) — injecting class names
@Styled is a field decorator that processes a Stylesheet subclass and injects a class name map into the component. Each $-prefixed field becomes a scoped class name string.
import { cx, Stylesheet, Styled } from '@praxisjs/css'
import { Component, State } from '@praxisjs/decorators'
import { StatefulComponent } from '@praxisjs/core'
class CardStyles extends Stylesheet {
$root = this.css({ display: 'flex', gap: '12px', padding: '16px', borderRadius: '8px' })
$selected = this.css({ border: '2px solid var(--accent)' })
$title = this.css({ fontSize: '1rem', fontWeight: 600 })
}
@Component()
class Card extends StatefulComponent {
@State() selected = false
@Styled(CardStyles)
$card!: CardStyles
render() {
return (
<div class={() => cx(this.$card.$root, { [this.$card.$selected]: this.selected })}>
<h3 class={this.$card.$title}>Card</h3>
</div>
)
}
}On StatelessComponent
Plain Stylesheet (no @Param()) works on stateless components. Class names are static and injected once, shared across all instances.
import { Component } from '@praxisjs/decorators'
import { StatelessComponent } from '@praxisjs/core'
@Component()
class Badge extends StatelessComponent<{ label: string; variant?: 'success' | 'error' }> {
@Styled(BadgeStyles) $badge!: BadgeStyles
render() {
const v = this.props.variant ?? 'success'
return (
<span class={cx(this.$badge.$root, this.$badge[`$${v}` as keyof BadgeStyles] as string)}>
{this.props.label}
</span>
)
}
}How scoping works
Each $-prefixed field gets a globally unique class name derived from a hash of its CSS content — for example prx-root-a1b2c3. Two fields with identical CSS share the same name. Class names are deterministic and stable.
Runtime mode (default / dev server): <style> elements are injected eagerly at decoration time (module load), before any component mounts. Each instance mount/unmount increments/decrements an internal reference count, but the base injection from decoration time is permanent — styles stay for the page lifetime. Identical CSS across different components shares one injection.
Static mode (production + praxisjsCSS() plugin): class names are identical but CSS is extracted at build time into virtual:praxisjs/styles.css. No <style> elements are injected at runtime — the CSS is part of the bundle from the start, fully cacheable by the browser. See Static extraction.
Multiple stylesheet fields
A component can hold as many Stylesheet fields as needed. Each one injects its own scoped stylesheet independently:
class LayoutStyles extends Stylesheet {
$container = this.css({ display: 'grid', gridTemplateColumns: '1fr 3fr', gap: '24px' })
$sidebar = this.css({ padding: '16px', background: '#f9fafb' })
}
class TypographyStyles extends Stylesheet {
$heading = this.css({ fontSize: '1.5rem', fontWeight: 700, lineHeight: 1.2 })
$body = this.css({ fontSize: '0.9375rem', lineHeight: 1.6, color: '#374151' })
}
@Component()
class Article extends StatefulComponent {
@Styled(LayoutStyles) $layout!: LayoutStyles
@Styled(TypographyStyles) $type!: TypographyStyles
render() {
return (
<div class={this.$layout.$container}>
<aside class={this.$layout.$sidebar}>…</aside>
<main>
<h1 class={this.$type.$heading}>Title</h1>
<p class={this.$type.$body}>…</p>
</main>
</div>
)
}
}