Components are functions that return JSX, in two kinds: server components and client components.
#Server Components
Server components render HTML on the server with no client-side JavaScript.
export function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}</h1>
}Server components can access databases, read files, and use secrets. They produce a template rendered once per request.
#Client Components
Client components use reactive primitives and ship JavaScript to the browser. They require the "use client" directive:
"use client"
import { createSignal } from '@barefootjs/client'
export function Counter() {
const [count, setCount] = createSignal(0)
return (
<button onClick={() => setCount(n => n + 1)}>
Count: {count()}
</button>
)
}The compiler produces a marked template (server HTML with bf-* attributes) and client JS (signals, effects, event handlers). See How It Works for details.
#When `"use client"` Is Required
Add "use client" when a component uses:
createSignal,createEffect,createMemoonMount,onCleanup,untrackcreateContext,useContext- Event handlers (
onClick,onChange, etc.)
Without the directive:
error[BF001]: 'use client' directive required for components with createSignal#Component Naming
Component names must start with an uppercase letter:
// ✅ Component
function TodoItem() { ... }
// ❌ Error BF042
function todoItem() { ... }#Compilation Output
Source:
"use client"
import { createSignal } from '@barefootjs/client'
export function Toggle() {
const [on, setOn] = createSignal(false)
return (
<button onClick={() => setOn(prev => !prev)}>
{on() ? 'ON' : 'OFF'}
</button>
)
}Marked template:
export function Toggle({ __instanceId, ... }) {
const __scopeId = __instanceId || `Toggle_${...}`
const on = () => false
return (
<button bf-s={__scopeId} bf="s1">
{on() ? <>{bfComment("cond-start:s0")}{'ON'}{bfComment("cond-end:s0")}</>
: <>{bfComment("cond-start:s0")}{'OFF'}{bfComment("cond-end:s0")}</>}
</button>
)
}
{{define "Toggle"}}
<button bf-s="{{bfScopeAttr .}}" bf="s1">
{{if .On}}{{bfComment "cond-start:s0"}}{{"ON"}}{{bfComment "cond-end:s0"}}
{{else}}{{bfComment "cond-start:s0"}}{{"OFF"}}{{bfComment "cond-end:s0"}}{{end}}
</button>
{{end}}
Client JS:
import { $, createSignal, hydrate, insert } from '@barefootjs/client'
export function initToggle(__scope, _p = {}) {
if (!__scope) return
const [on, setOn] = createSignal(false)
const [_s1, _s0] = $(__scope, 's1', 's0')
insert(__scope, 's0', () => on(), {
template: () => `<!--bf-cond-start:s0-->ON<!--bf-cond-end:s0-->`,
bindEvents: (__branchScope) => {}
}, {
template: () => `<!--bf-cond-start:s0-->OFF<!--bf-cond-end:s0-->`,
bindEvents: (__branchScope) => {}
})
if (_s1) _s1.addEventListener('click', () => { setOn(prev => !prev) })
}
hydrate('Toggle', { init: initToggle, template: ... })Only the conditional branch bound to on() updates when the signal changes. The insert() function handles DOM swapping using comment markers as boundaries.
#Composition Rules
| From | To | Allowed |
|---|---|---|
| Server component | Server component | ✅ |
| Server component | Client component | ✅ |
| Client component | Client component | ✅ |
| Client component | Server component | ❌ |
Server-only code does not exist in the browser. The compiler emits BF003 if a client component imports a server component.
// Page.tsx — server component
import { Counter } from './Counter' // "use client" ✅
import { UserList } from './UserList' // server-only ✅
export function Page() {
return (
<div>
<UserList /> {/* Server → Server */}
<Counter /> {/* Server → Client */}
</div>
)
}// Dashboard.tsx — "use client"
import { Counter } from './Counter' // ✅ Client → Client
import { UserList } from './UserList' // ❌ BF003: Client → Server#Ref Callbacks
ref callbacks provide imperative DOM access. The callback receives the element after mount:
"use client"
import { createEffect } from '@barefootjs/client'
export function AutoFocus() {
const handleMount = (el: HTMLInputElement) => {
el.focus()
}
return <input ref={handleMount} placeholder="Focused on mount" />
}Combine with createEffect for reactive DOM updates:
const handleMount = (el: HTMLElement) => {
createEffect(() => {
el.className = isActive() ? 'active' : 'inactive'
})
}