PraxisJS

DOM Utilities

@praxisjs/composables DOM composable classes — WindowSize, ScrollPosition, ElementSize, Intersection, and Focus. Used via the @Compose decorator.

DOM Utilities

DOM composables from @praxisjs/composables. Attach them to any component with @Compose to bind reactive DOM state directly to component properties.

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

WindowSize

Tracks the browser window's inner dimensions reactively. Updates on every resize event.

import { Compose, Ref } from '@praxisjs/decorators'
import { WindowSize } from '@praxisjs/composables'

@Component()
class App extends StatefulComponent {
  @Compose(WindowSize)
  window!: WindowSize

  render() {
    return (
      <div>
        <p>Viewport: {() => this.window.width} × {() => this.window.height}</p>
        {() => this.window.width < 768 && <MobileMenu />}
      </div>
    )
  }
}

Properties: width: number, height: number

Storybook
Live demo — WindowSize

ScrollPosition

Tracks scroll position. Omit the argument to track the window scroll; pass a ref string to track a specific scrollable element.

// Track window scroll
@Compose(ScrollPosition)
scroll!: ScrollPosition

render() {
  return <p>Scroll Y: {() => this.scroll.y}px</p>
}
// Track a specific scrollable element
@Ref<HTMLDivElement>()
containerRef!: Ref<HTMLDivElement>

@Compose(ScrollPosition, 'containerRef')
scroll!: ScrollPosition

render() {
  return (
    <div
      ref={this.containerRef}
      style="height:300px;overflow:auto"
    >
      <p>Scroll: {() => this.scroll.y}px</p>
      {/* scrollable content */}
    </div>
  )
}

Pass the ref name as a string

Pass the property name as a string ('containerRef') to @Compose. The element doesn't exist at decoration time — the string is resolved to the Ref object at mount when the DOM is ready.

Properties: x: number, y: number

Storybook
Live demo — ScrollPosition

ElementSize

Tracks an element's dimensions reactively via ResizeObserver. Updates whenever the element's size changes.

@Component()
class ResizeWatcher extends StatefulComponent {
  @Ref<HTMLDivElement>()
  containerRef!: Ref<HTMLDivElement>

  @Compose(ElementSize, 'containerRef')
  size!: ElementSize

  render() {
    return (
      <div ref={this.containerRef}>
        <p>Width: {() => this.size.width}px, Height: {() => this.size.height}px</p>
      </div>
    )
  }
}

Properties: width: number, height: number

Storybook
Live demo — ElementSize

Intersection

Tracks whether an element is intersecting the viewport (or a scroll container) via IntersectionObserver.

@Component()
class LazySection extends StatefulComponent {
  @Ref<HTMLElement>()
  sectionRef!: Ref<HTMLElement>

  @Compose(Intersection, 'sectionRef', { threshold: 0.5 })
  visibility!: Intersection

  render() {
    return (
      <section ref={this.sectionRef}>
        {() => this.visibility.visible ? <HeavyContent /> : <Placeholder />}
      </section>
    )
  }
}

Constructor: new Intersection(ref, options?)options matches IntersectionObserverInit.

Properties: visible: boolean

Storybook
Live demo — Intersection

Focus

Tracks whether an element currently has focus.

@Component()
class SearchBar extends StatefulComponent {
  @Ref<HTMLInputElement>()
  inputRef!: Ref<HTMLInputElement>

  @Compose(Focus, 'inputRef')
  focus!: Focus

  render() {
    return (
      <div>
        <input ref={this.inputRef} placeholder="Search..." />
        {() => this.focus.focused && <span class="hint">Press Enter to search</span>}
      </div>
    )
  }
}

Properties: focused: boolean

Storybook
Live demo — Focus

On this page