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
Spacing
gap: 8px · padding: 16px
Typography
Custom Tokens
2 tokens
Preview
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.