Morphos & Kosmesis are here.Explore
PraxisJS

Browser APIs

@praxisjs/composables browser composable classes — MediaQuery, ColorScheme, Mouse, KeyCombo, Idle, Clipboard, Geolocation, TimeAgo, and Pagination.

Browser APIs

Browser API composables from @praxisjs/composables. Bind them to any component with @Compose to get reactive browser state as class properties.

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

MediaQuery

Reactive CSS media query matching. Updates whenever the query result changes.

@Compose(MediaQuery, '(max-width: 768px)')
mobile!: MediaQuery

render() {
  return () => this.mobile.matches ? <MobileView /> : <DesktopView />
}

Constructor: new MediaQuery(query: string) Properties: matches: boolean

Storybook
Live demo — MediaQuery

ColorScheme

Detects the user's preferred color scheme via prefers-color-scheme. Updates if the user changes their system preference.

@Compose(ColorScheme)
scheme!: ColorScheme

render() {
  return <div class={() => this.scheme.isDark ? 'dark' : 'light'}>...</div>
}

Properties: isDark: boolean, isLight: boolean

Storybook
Live demo — ColorScheme

Mouse

Tracks the cursor position in the viewport.

@Compose(Mouse)
mouse!: Mouse

render() {
  return (
    <div
      style={() => ({
        transform: `translate(${this.mouse.x}px, ${this.mouse.y}px)`
      })}
    />
  )
}

Properties: x: number, y: number

Storybook
Live demo — Mouse

KeyCombo

Detects keyboard shortcut combinations. The pressed signal becomes true while the combo is held.

@Compose(KeyCombo, 'ctrl+s')
saveShortcut!: KeyCombo

onMount() {
  effect(() => {
    if (this.saveShortcut.pressed) this.save()
  })
}

Constructor: new KeyCombo(combo: string) — accepts ctrl, shift, alt, meta modifiers with + as separator. Properties: pressed: boolean

Storybook
Live demo — KeyCombo

Idle

Detects user inactivity by monitoring mouse, keyboard, and touch events. Default timeout is 60 seconds.

@Compose(Idle, 30_000)  // idle after 30s of inactivity
activity!: Idle

render() {
  return () => this.activity.idle ? <ScreenSaver /> : <AppContent />
}

Constructor: new Idle(timeout?: number) — timeout in ms. Properties: idle: boolean

Storybook
Live demo — Idle (5s)

Clipboard

Read/write clipboard access. copied resets to false after resetDelay ms (default: 2000).

@Compose(Clipboard)
clipboard!: Clipboard

render() {
  return (
    <button onClick={() => this.clipboard.copy('Hello!')}>
      {() => this.clipboard.copied ? '✓ Copied!' : 'Copy'}
    </button>
  )
}

Properties: copied: boolean, content: string, copy(text): Promise<void>

Storybook
Live demo — Clipboard

Geolocation

One-shot geolocation lookup via the Geolocation API.

@Compose(Geolocation, { enableHighAccuracy: true })
geo!: Geolocation

render() {
  return (
    <div>
      {() => this.geo.loading && <p>Locating...</p>}
      {() => this.geo.error && <p>Location access denied.</p>}
      {() => !this.geo.loading && !this.geo.error && (
        <p>Lat: {this.geo.lat?.toFixed(4)}, Lon: {this.geo.lng?.toFixed(4)}</p>
      )}
    </div>
  )
}

Constructor: new Geolocation(options?: PositionOptions) Properties: lat: number | null, lng: number | null, error: GeolocationPositionError | null, loading: boolean


TimeAgo

Relative time formatting ("3 minutes ago", "2 days ago"), updated automatically while the component is mounted. Instead of polling, it sets one timer for the exact moment the text changes: every second for "30 seconds ago", once an hour or less for "3 days ago". The timer is re-armed when the source changes.

import { getter } from '@praxisjs/decorators'

@State() postedAt = new Date('2026-01-01')

// getter() wraps the property as () => this.postedAt — TimeAgo needs a callable source
@Compose(TimeAgo, getter('postedAt'))
timeAgo!: TimeAgo

render() {
  return <p>Posted {() => this.timeAgo.value}</p>
  // → "4 months ago"
}

Constructor: new TimeAgo(source: Signal<Date> | (() => Date), locale?: string) Properties: value: string

Storybook
Live demo — TimeAgo

Pagination

Manages pagination state — current page, total pages, and navigation methods.

@Compose(Pagination, { total: 100, pageSize: 10 })
pages!: Pagination

render() {
  return (
    <div>
      <p>Page {() => this.pages.page} of {() => this.pages.totalPages}</p>
      <button disabled={() => !this.pages.hasPrev} onClick={() => this.pages.prev()}>← Prev</button>
      <button disabled={() => !this.pages.hasNext} onClick={() => this.pages.next()}>Next →</button>
    </div>
  )
}

Constructor: new Pagination({ total, pageSize, initial? })

Property/MethodDescription
pageCurrent page (1-based)
totalPagesTotal number of pages
offsetItems to skip (useful for API offset or array slice)
hasPrev / hasNextNavigation availability
prev() / next()Navigate one page
goTo(n)Jump to a specific page
first() / last()Jump to first or last page
Storybook
Live demo — Pagination

On this page