A BarefootJS component is a function that returns JSX. Components come in two kinds: server components and client components.
#Server Components
A server component renders HTML on the server. It has no client-side JavaScript.
export function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}</h1>
}Server components can access databases, read files, use secrets — anything that should stay on the server. They produce a template that is rendered once per request.
#Client Components
A client component uses reactive primitives and ships JavaScript to the browser. It requires the "use client" directive at the top of the file:
"use client"
import { createSignal } from '@barefootjs/dom'
export function Counter({ initial = 0 }) {
const [count, setCount] = createSignal(initial)
return (
<div>
<p>{count()}</p>
<button onClick={() => setCount(n => n + 1)}>+1</button>
</div>
)
}The compiler produces two outputs from this source:
- Marked Template — Server-rendered HTML with
bf-*attributes - Client JS — A minimal script that creates signals, binds effects, and attaches event handlers
See Core Concepts — Two-Phase Compilation for details.
#When `"use client"` Is Required
Add "use client" when a component uses any of these:
createSignal,createEffect,createMemoonMount,onCleanup,untrackcreateContext,useContext- Event handlers (
onClick,onChange, etc.)
Without the directive, the compiler emits an error:
error[BF001]: 'use client' directive required for components with createSignal#Component Naming
Component names must start with an uppercase letter. This is how the compiler distinguishes components from HTML elements:
// ✅ Component
function TodoItem() { ... }
// ❌ Error BF042
function todoItem() { ... }#Compilation Output
A client component compiles into a server template and a client init function. Here is a minimal example to illustrate the full pipeline:
Source:
"use client"
import { createSignal } from '@barefootjs/dom'
export function Toggle() {
const [on, setOn] = createSignal(false)
return (
<button onClick={() => setOn(v => !v)}>
{on() ? 'ON' : 'OFF'}
</button>
)
}Server template (Hono):
export function Toggle() {
return (
<button bf-s="Toggle" bf="slot_0">
OFF
</button>
)
}
{{define "Toggle"}}
<button bf-s="{{.ScopeID}}" bf="slot_0">
OFF
</button>
{{end}}
Client JS:
import { createSignal, createEffect, find, bind } from '@barefootjs/dom'
export function init() {
const [on, setOn] = createSignal(false)
const _slot_0 = find('[bf="slot_0"]')
createEffect(() => { _slot_0.textContent = on() ? 'ON' : 'OFF' })
bind('[bf="slot_0"]', 'click', () => setOn(v => !v))
}The server renders static HTML. The browser runs the init function to make it interactive. Only the specific text node bound to on() updates when the signal changes.
#Composition Rules
Server and client components follow a one-way composition rule:
| From | To | Allowed |
|---|---|---|
| Server component | Server component | ✅ |
| Server component | Client component | ✅ |
| Client component | Client component | ✅ |
| Client component | Server component | ❌ |
A client component cannot import a server component because server-only code does not exist in the browser. The compiler emits error BF003 if this is attempted.
// 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 → ServerThink of "use client" as a one-way gate: once you cross into client territory, everything below must also be a client component.
#Ref Callbacks
Client components use ref callbacks for imperative DOM access. The callback receives the DOM element after it is mounted:
"use client"
import { createEffect } from '@barefootjs/dom'
export function AutoFocus() {
const handleMount = (el: HTMLInputElement) => {
el.focus()
}
return <input ref={handleMount} placeholder="Focused on mount" />
}Ref callbacks are the primary mechanism for attaching side effects to specific elements. They are often combined with createEffect for reactive DOM updates:
const handleMount = (el: HTMLElement) => {
createEffect(() => {
el.className = isActive() ? 'active' : 'inactive'
})
}