Get Started
Introduction
Components
Accordion
Badge
Button
Card
Checkbox
Command
Dialog
Dropdown Menu
Input
Select
Switch
Table
Tabs
Toast
Tooltip
Forms
Introduction
Validation
Field Arrays

Dashboard Builder

Dynamic widget composition where each widget owns its own signal scope. Add, remove, reorder, and resize heterogeneous widgets; the layout grid reconfigures from a memo driven by widget count.

#Preview

Dashboard Builder

4 widgets
Stat: 1·Progress: 1·Todo: 1·Chart: 1
Add:
Stat
128
±0
Progress
35%
Behind
Todo
2 of 3 remaining
Review PRs
Ship release
Write docs
Chart
Total
236
Q1Q2Q3Q4
1"use client"23import { createSignal, createMemo } from '@barefootjs/client'45// Each widget type is its own client component with its own signal scope.6// Two StatWidget instances each maintain independent value/trend signals —7// incrementing one does not touch the other.89function StatWidget(props: { initialValue: number; step: number }) {10  const [value, setValue] = createSignal(props.initialValue)11  const delta = createMemo(() => value() - props.initialValue)12  return (13    <div>14      <div>{value()}</div>15      <button onClick={() => setValue(value() + props.step)}>+</button>16    </div>17  )18}1920function ProgressWidget(props: { initialProgress: number }) {21  const [progress, setProgress] = createSignal(props.initialProgress)22  // ...own bar + status memos23}2425function TodoWidget() { /* own todos signal */ }26function ChartWidget(props: { initialBars: ChartBar[] }) { /* own bars + selectedIndex */ }2728type WidgetConfig = { id: number; type: 'stat' | 'progress' | 'todo' | 'chart'; title: string; size: 'sm' | 'md' | 'lg' }2930function DashboardBuilder() {31  const [widgets, setWidgets] = createSignal<WidgetConfig[]>(initialWidgets)3233  // Layout memo: grid column class recomputed whenever widgets are added/removed34  const gridCols = createMemo(() => {35    const n = widgets().length36    if (n <= 1) return 'grid-cols-1'37    if (n === 2) return 'grid-cols-1 md:grid-cols-2'38    return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'39  })4041  return (42    <div className={`grid gap-3 ${gridCols()}`}>43      {widgets().map(w => (44        <div key={w.id} className="widget-cell">45          {/* Dynamic component switching inside the loop body */}46          {w.type === 'stat' ? <StatWidget initialValue={...} step={...} /> : null}47          {w.type === 'progress' ? <ProgressWidget initialProgress={35} /> : null}48          {w.type === 'todo' ? <TodoWidget /> : null}49          {w.type === 'chart' ? <ChartWidget initialBars={...} /> : null}50        </div>51      ))}52    </div>53  )54}

#Features

Per-Widget Signal Isolation

Each widget type (Stat, Progress, Todo, Chart) is a separate client component with its own signal scope. Adding a second StatWidget creates an entirely new reactive instance — its value signal, trend memo, and DOM bindings are independent of every other StatWidget on the page. Incrementing one StatWidget does not invalidate memos in sibling widgets.

Dynamic Component Switching Inside .map()

The widget loop body renders a different child component based onw.type. A ternary chain returnsStatWidget, ProgressWidget,TodoWidget, or ChartWidget. Each branch produces a structurally different subtree — the compiler must hydrate the right component per item and wire up its independent signal tree.

Layout Memo Dependent on Widget Count

gridCols memo readswidgets().length and emits a responsive Tailwind grid class. Adding or removing widgets updates the memo, which updates the container's className via a reactive CSS class binding — without re-creating the widget instances.

Loop Rebuild With Fresh Child State

Removing a widget filters the config array; adding one appends. Each new child widget mounts with a clean signal scope, initialized from constructor props. This exercises the compiler's keyed-reconciliation path inside .map() bodies that emit distinct component types.

Derived Count Badges

Four independent memos (stat / progress / todo / chart count) each filter widgets() by type and drive a badge. All four recompute on any widget list change, testing fan-out from a single upstream signal.