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

Theme Customizer

Three signal-driven context providers (palette, spacing, typography) wrapping a 12-level deep consumer tree. Tests Provider value propagation across deep trees, multi-provider ordering, stale-read safety, and dynamic token add/remove.

#Preview

Palette

#3b82f6
#64748b
#f59e0b

Spacing

gap: 8px · padding: 16px

Typography

Custom Tokens

--brand-glow#93c5fd
--surface-dim#f1f5f9

2 tokens

Preview

A
AppName live
Analytics
Revenue$12,450
Users2,847
Conversion4.2%
1"use client"23import { createContext, useContext, createSignal, createMemo, createEffect } from '@barefootjs/client'45// Three separate contexts — each with a signal-driven provider value67const PaletteCtx = createContext<{ primary: () => string; secondary: () => string; accent: () => string }>()8const SpacingCtx = createContext<{ scale: () => SpacingScale; gap: () => string; padding: () => string }>()9const TypographyCtx = createContext<{ fontFamily: () => string; fontSize: () => string }>()1011export function ThemeCustomizer() {12  const [primary, setPrimary] = createSignal('#3b82f6')13  const [secondary, setSecondary] = createSignal('#64748b')14  const [accent, setAccent] = createSignal('#f59e0b')15  const [spacingScale, setSpacingScale] = createSignal<SpacingScale>('normal')16  const gap = createMemo(() => SPACING[spacingScale()].gap)17  const padding = createMemo(() => SPACING[spacingScale()].padding)18  const [fontFamily, setFontFamily] = createSignal<FontFamily>('sans')19  const [fontSize, setFontSize] = createSignal<FontSize>('base')20  const fontFamilyCss = createMemo(() => FONT_FAMILIES[fontFamily()])21  const fontSizeCss = createMemo(() => FONT_SIZES[fontSize()])2223  return (24    // Multi-provider nesting: all three providers wrap the same tree.25    // PaletteCtx values resolve before SpacingCtx, before TypographyCtx.26    <PaletteCtx.Provider value={{ primary, secondary, accent }}>27      <SpacingCtx.Provider value={{ scale: spacingScale, gap, padding }}>28        <TypographyCtx.Provider value={{ fontFamily: fontFamilyCss, fontSize: fontSizeCss }}>29          {/* Controls + 12-level deep preview tree */}30        </TypographyCtx.Provider>31      </SpacingCtx.Provider>32    </PaletteCtx.Provider>33  )34}3536// Consumer 12 levels deep — reads PaletteCtx37function PreviewCardValue(props: { label: string; accent: boolean }) {38  const handleMount = (el: HTMLElement) => {39    const palette = useContext(PaletteCtx)40    createEffect(() => {41      // Re-runs whenever primary() or accent() signal changes42      el.style.color = props.accent ? palette.accent() : palette.primary()43    })44  }45  return <span ref={handleMount}>{props.label}</span>46}4748// Consumer 10 levels deep — reads BOTH PaletteCtx and TypographyCtx49function PreviewCardHeader(props: { title: string }) {50  const handleMount = (el: HTMLElement) => {51    const palette = useContext(PaletteCtx)52    const typography = useContext(TypographyCtx)53    createEffect(() => {54      el.style.color = palette.primary()55      el.style.fontFamily = typography.fontFamily()56      el.style.fontSize = typography.fontSize()57    })58  }59  return <div ref={handleMount}>{props.title}</div>60}

#Features

Multi-Provider Nesting

Three independent contexts —PaletteCtx,SpacingCtx, andTypographyCtx — are all active simultaneously. The compiler must maintain separate context stacks for each and correctly resolve which Provider owns which consumer.PreviewCardHeader (level 10) reads from both PaletteCtx andTypographyCtx in the same mount callback, exercising multi-provider ordering.

12-Level Deep Consumer Tree

The preview tree runs from the root toPreviewBadgeLabel andPreviewCardValue at level 12, with context consumers at every level. Changing the primary color updates the brand icon (level 9), the header brand (level 8), nav chips (level 9), the sidebar active item (level 10), the card header (level 10), and the card value (level 12) — all simultaneously on a single signal write.

Stale-Read Safety

Provider values contain signal getter references (e.g. { primary, secondary, accent }) not snapshot values. Consumers callctx.primary() insidecreateEffect, subscribing to the live signal. Changing the primary color while the secondary is also changing (e.g., via rapid input) tests that no consumer reads a stale snapshot mid-render-chain.

Dynamic Token List

The custom tokens array is acreateSignal<CustomToken[]>. Adding or removing a token re-runs thecustomTokens().map() loop in JSX, exercising the compiler's key-based list reconciliation for a signal-driven loop whose length changes at runtime.