Hydration is the process of making server-rendered HTML interactive. BarefootJS uses a marker-driven approach.
#Hydration Markers
The compiler inserts bf-* attributes into the marked template. These tell the client JS where to attach behavior:
| Marker | Purpose | Example |
|---|---|---|
bf-s |
Component scope boundary (~ prefix = child) |
<div bf-s="Counter_a1b2">, <div bf-s="~Item_c3d4"> |
bf |
Interactive element (slot) | <p bf="s0"> |
bf-p |
Serialized props JSON | <div bf-p='{"initial":5}'> |
bf-c |
Conditional block | <div bf-c="s2"> |
bf-po |
Portal owner scope ID | <div bf-po="Dialog_a1b2"> |
bf-pi |
Portal container ID | <div bf-pi="bf-portal-1"> |
bf-pp |
Portal placeholder | <template bf-pp="bf-portal-1"> |
bf-i |
List item marker | <li bf-i> |
#Hydration Flow
- The server renders HTML with markers and embeds component props in
bf-pattributes - The browser loads the client JS
hydrate()finds all uninitializedbf-selements- For each scope, the init function runs — creating signals, binding effects, attaching event handlers
- The runtime tracks the scope internally to prevent double initialization
- The page is now interactive
Server HTML (static)
↓
Client JS loads
↓
hydrate('Counter', { init: initCounter })
↓
Find uninitialized <div bf-s="Counter_a1b2">
↓
Read props from bf-p attribute
↓
Run init(): createSignal, createEffect, attach event handlers
↓
Track scope as initialized (internal hydratedScopes Set)
↓
Page is interactive#Scoped Queries
Each component only hydrates its own elements. The runtime's find() function searches within a scope boundary, excluding nested component scopes. This prevents components from interfering with each other.
<div bf-s="TodoApp_x1"> <!-- TodoApp scope -->
<h1 bf="slot_0">Todo</h1> <!-- belongs to TodoApp -->
<div bf-s="~TodoItem_y1"> <!-- TodoItem scope (~ = child, excluded from TodoApp queries) -->
<span bf="slot_0">Buy milk</span>
</div>
</div>When TodoApp's init calls find(scope, '[bf="slot_0"]'), it finds the <h1>, not the <span> inside TodoItem.