# pream
[](https://hex.pm/packages/pream)
[](https://hexdocs.pm/pream/)
[](LICENSE)
Signals-first Gleam bindings for [Preact](https://preactjs.com/) with
[`@preact/signals`](https://github.com/preactjs/signals) integration.
Re-rendering is driven by signals, not component state.
## Philosophy
pream is signals-first. Components don't hold local state — they read from
signals and re-render when signals change. This means:
- No `useState` / `useReducer` — use `signal.new` and `signal.set` instead
- No manual dependency arrays for signal effects — use `use_signal_effect`
- Component boundaries preserve Preact devtools and signal-driven re-rendering
## Install
```sh
gleam add pream
```
Requires Preact and `@preact/signals` as npm dependencies:
```sh
npm install preact @preact/signals
```
## Quick start
```gleam
import pream
import pream/signal
import pream/vnode
pub fn main() {
let count = signal.new(0)
let app =
vnode.div()
|> vnode.children([
vnode.text("Clicked "),
vnode.reactive_text(signal.map(count, fn(n) { int.to_string(n) })),
vnode.text(" times"),
vnode.element(
vnode.button()
|> vnode.on("click", fn(_) { signal.setter(count, fn(c) { c + 1 }) })
|> vnode.child(vnode.text("Increment")),
),
])
pream.to_preact(app)
}
```
## Component boundaries
Use `vnode.component` to create a Preact component boundary. This preserves
Preact devtools visibility and ensures signal-driven re-rendering is scoped
to the component. Static values are passed via closure capture; dynamic
values flow through signals.
```gleam
import pream
import pream/hooks
import pream/signal
import pream/vnode
fn counter() -> vnode.VNode {
let count = hooks.use_signal(0)
vnode.new("div")
|> vnode.child(vnode.reactive_text(signal.map(count, int.to_string)))
|> vnode.child(vnode.element(
vnode.button()
|> vnode.on("click", fn(_) { signal.setter(count, fn(c) { c + 1 }); Nil })
|> vnode.child(vnode.text("+1")),
))
}
pub fn main() -> pream.PreactComponent {
vnode.new("main")
|> vnode.child(vnode.text("Counter Demo"))
|> vnode.child(vnode.component(counter))
|> pream.to_preact()
}
```
Functions with arguments need a wrapping closure to capture state:
```gleam
fn greeting(name: String) -> vnode.VNode {
vnode.new("div")
|> vnode.child(vnode.text("Hello, " <> name))
}
// In another component:
vnode.new("main")
|> vnode.child(vnode.component(fn() { greeting("Alice") }))
```
Dynamic values use signals instead of changing props:
```gleam
fn counter_display(count: signal.Signal(Int)) -> vnode.VNode {
vnode.new("span")
|> vnode.child(vnode.reactive_text(signal.map(count, int.to_string)))
}
// In another component:
vnode.new("main")
|> vnode.child(vnode.component(fn() { counter_display(some_signal) }))
```
## Examples
### Counter with signal
```gleam
import pream/signal
import pream/vnode
pub fn counter() {
let count = signal.new(0)
vnode.div()
|> vnode.child(vnode.reactive_text(signal.map(count, fn(n) {
"Count: " <> int.to_string(n)
})))
|> vnode.child(vnode.element(
vnode.button()
|> vnode.on("click", fn(_) {
signal.setter(count, fn(c) { c + 1 })
Nil
})
|> vnode.child(vnode.text("Increment")),
))
}
```
### Conditional rendering
```gleam
import pream/vnode
import pream/signal
pub fn visibility_toggle() {
let visible = signal.new(True)
vnode.div()
|> vnode.child(vnode.when_signal(visible, fn() {
vnode.text("Hello, world!")
}))
|> vnode.child(vnode.element(
vnode.button()
|> vnode.on("click", fn(_) {
signal.setter(visible, fn(v) { !v })
Nil
})
|> vnode.child(vnode.text("Toggle")),
))
}
```
### Using useEffect
```gleam
import pream/hooks
import pream/dom
pub fn timer_component() {
let count = hooks.use_ref(0)
hooks.use_effect(fn() {
let id = setInterval(fn() { count.current = count.current + 1 }, 1000)
fn() { clearInterval(id) }
}, [])
vnode.new("div") |> vnode.child(vnode.text("Timer running"))
}
```
## Features
- **Signals-first** — re-rendering driven by signals, not component state
- **Component boundaries** — `vnode.component` preserves Preact devtools and
signal-driven re-rendering
- **Shorthand constructors** — `vnode.div()`, `vnode.span()`, `vnode.button()`,
etc.
- **Pipe-friendly modifiers** — `prop`, `on`, `class`, `id`, `disabled`, ...
- **Fine-grained signals** — `signal.new`, `signal.computed`, `signal.effect`
- **Component hooks** — `use_signal`, `use_signal_effect`, `use_computed`
- **Preact hooks** — `use_effect`, `use_memo`, `use_callback`, `use_ref`,
`use_id`, ...
- **QoL hooks** — `use_mount`, `use_unmount`
- **Conditional helpers** — `vnode.when`, `vnode.unless`, `vnode.when_some`,
`vnode.when_signal`, `vnode.map_signal`
- **Result/Option-aware** — `pream.unwrap` and `pream.unwrap_option` silently
degrade into empty nodes on `Error`/`None`
## Hooks
All hooks are available from `import pream/hooks`.
### Signal hooks
| Hook | Description |
| ------------------------ | ---------------------------------------------- |
| `use_signal(initial)` | Create a reactive signal scoped to a component |
| `use_signal_effect(run)` | Run a reactive effect on signal changes |
| `use_computed(fn)` | Create a computed signal scoped to a component |
### Effect hooks
| Hook | Description |
| -------------------------------------- | -------------------------------------- |
| `use_effect(run, deps)` | Side effect after render (no cleanup) |
| `use_effect_cleanup(run, deps)` | Side effect with cleanup function |
| `use_layout_effect(run, deps)` | Synchronous effect after DOM mutations |
| `use_layout_effect_cleanup(run, deps)` | Synchronous effect with cleanup |
### Memoization hooks
| Hook | Description |
| ------------------------------ | -------------------------------- |
| `use_memo(compute, deps)` | Memoize an expensive computation |
| `use_callback(callback, deps)` | Memoize a callback function |
### Ref hooks
| Hook | Description |
| ------------------------------------------ | --------------------------------- |
| `use_ref(initial)` | Create a mutable reference object |
| `use_imperative_handle(ref, create, deps)` | Customize ref handle |
### Misc hooks
| Hook | Description |
| ------------------------ | -------------------------------------- |
| `use_id()` | Unique ID for accessibility attributes |
| `use_debug_value(value)` | Custom devtools label |
### QoL hooks
| Hook | Description |
| ----------------- | ------------------- |
| `use_mount(fn)` | Run once on mount |
| `use_unmount(fn)` | Run once on unmount |
## Documentation
- API reference: <https://hexdocs.pm/pream>
- Source code: <https://github.com/soulsam480/pream>
## License
MIT © 2026 soulsam480