Customize the UI Builder's interface by configuring panels, tab labels, and adding your own components to the editor. The panelConfig
prop gives you complete control over the editor's layout and functionality.
Understanding Panel Configuration
UI Builder's interface consists of several configurable panels:
- Left Panel (Page Config): Contains tabs for Layers, Appearance, and Variables
- Center Panel (Editor): The main canvas and preview area
- Right Panel (Properties): Component property editing forms
- Navigation Bar: Top navigation and controls
The panelConfig
prop allows you to:
- ✅ Customize tab labels in the left panel
- ✅ Replace tab content with your own components
- ✅ Add new tabs with custom functionality
- ✅ Remove unwanted tabs for simplified interfaces
- ✅ Override entire panels with custom implementations
Interactive Demo
Try the different configurations below to see how panelConfig
works:
Basic Usage
Default Configuration
By default, UI Builder includes three tabs in the left panel:
tsx1import UIBuilder from '@/components/ui/ui-builder'; 2 3// Uses default panel configuration 4<UIBuilder componentRegistry={myRegistry} />
This gives you:
- Layers: Component tree and page management
- Appearance: Tailwind theme and styling controls
- Data: Variable management and binding
Custom Tab Labels
Rename tabs to match your workflow:
tsx1import UIBuilder, { defaultConfigTabsContent } from '@/components/ui/ui-builder'; 2 3const customPanelConfig = { 4 pageConfigPanelTabsContent: { 5 layers: { 6 title: "Structure", 7 content: defaultConfigTabsContent().layers.content 8 }, 9 appearance: { 10 title: "Design", 11 content: defaultConfigTabsContent().appearance?.content 12 }, 13 data: { 14 title: "Variables", 15 content: defaultConfigTabsContent().data?.content 16 } 17 } 18}; 19 20<UIBuilder 21 componentRegistry={myRegistry} 22 panelConfig={customPanelConfig} 23/>
Minimal Configuration
Show only essential panels:
tsx1const minimalPanelConfig = { 2 pageConfigPanelTabsContent: { 3 layers: { 4 title: "Structure", 5 content: defaultConfigTabsContent().layers.content 6 } 7 // Only show the layers tab, hide appearance and data 8 } 9}; 10 11<UIBuilder 12 componentRegistry={myRegistry} 13 panelConfig={minimalPanelConfig} 14/>
Custom Panel Content
Replace default panel content with your own React components:
Custom Appearance Panel
Create a branded design panel:
tsx1const CustomAppearancePanel = () => ( 2 <div className="p-4 space-y-4"> 3 <div className="text-sm font-medium">🎨 Brand Design System</div> 4 5 {/* Brand Colors */} 6 <div className="space-y-2"> 7 <div className="text-xs text-muted-foreground">Brand Colors</div> 8 <div className="grid grid-cols-4 gap-2"> 9 {brandColors.map((color) => ( 10 <div 11 key={color.name} 12 className="h-8 rounded border cursor-pointer" 13 style={{ backgroundColor: color.value }} 14 onClick={() => applyBrandColor(color)} 15 title={color.name} 16 /> 17 ))} 18 </div> 19 </div> 20 21 {/* Typography */} 22 <div className="space-y-2"> 23 <div className="text-xs text-muted-foreground">Typography Scale</div> 24 <div className="space-y-1"> 25 {typographyScale.map((scale) => ( 26 <div 27 key={scale.name} 28 className={`text-${scale.size} border-l-2 pl-2`} 29 style={{ borderColor: scale.color }} 30 > 31 {scale.name} 32 </div> 33 ))} 34 </div> 35 </div> 36 37 {/* Actions */} 38 <Button 39 size="sm" 40 className="w-full" 41 onClick={exportTheme} 42 > 43 Export Theme 44 </Button> 45 </div> 46); 47 48const customPanelConfig = { 49 pageConfigPanelTabsContent: { 50 layers: { title: "Layers", content: defaultConfigTabsContent().layers.content }, 51 appearance: { title: "Brand", content: <CustomAppearancePanel /> }, 52 data: { title: "Data", content: defaultConfigTabsContent().data?.content } 53 } 54};
Custom Data Panel
Integrate with your data sources:
tsx1const CustomDataPanel = () => { 2 const [dataSources, setDataSources] = useState([]); 3 const [isConnecting, setIsConnecting] = useState(false); 4 5 return ( 6 <div className="p-4 space-y-4"> 7 <div className="text-sm font-medium flex items-center gap-2"> 8 <Database className="h-4 w-4" /> 9 Data Sources 10 </div> 11 12 {/* Connected Sources */} 13 <div className="space-y-2"> 14 {dataSources.map((source) => ( 15 <div key={source.id} className="p-2 border rounded text-xs"> 16 <div className="font-medium flex items-center gap-2"> 17 <div className={`w-2 h-2 rounded-full ${ 18 source.connected ? 'bg-green-500' : 'bg-red-500' 19 }`} /> 20 {source.name} 21 </div> 22 <div className="text-muted-foreground"> 23 {source.connected 24 ? `Connected • ${source.recordCount} records` 25 : 'Disconnected' 26 } 27 </div> 28 </div> 29 ))} 30 </div> 31 32 {/* Add New Source */} 33 <Button 34 size="sm" 35 className="w-full" 36 onClick={openDataSourceDialog} 37 disabled={isConnecting} 38 > 39 {isConnecting ? 'Connecting...' : 'Add Data Source'} 40 </Button> 41 </div> 42 ); 43};
Adding New Tabs
Extend the interface with custom functionality:
tsx1const CustomSettingsPanel = () => ( 2 <div className="p-4 space-y-4"> 3 <div className="text-sm font-medium flex items-center gap-2"> 4 <Settings className="h-4 w-4" /> 5 Project Settings 6 </div> 7 8 <div className="space-y-3"> 9 <div className="space-y-1"> 10 <label className="text-xs text-muted-foreground">Environment</label> 11 <select className="w-full text-sm border rounded px-2 py-1"> 12 <option>Development</option> 13 <option>Staging</option> 14 <option>Production</option> 15 </select> 16 </div> 17 18 <div className="space-y-1"> 19 <label className="text-xs text-muted-foreground">Framework</label> 20 <input 21 type="text" 22 value="Next.js 14" 23 className="w-full text-sm border rounded px-2 py-1" 24 readOnly 25 /> 26 </div> 27 28 <div className="space-y-1"> 29 <label className="text-xs text-muted-foreground">Deploy Target</label> 30 <input 31 type="text" 32 value="Vercel" 33 className="w-full text-sm border rounded px-2 py-1" 34 readOnly 35 /> 36 </div> 37 </div> 38 </div> 39); 40 41const extendedPanelConfig = { 42 pageConfigPanelTabsContent: { 43 layers: { title: "Layers", content: defaultConfigTabsContent().layers.content }, 44 appearance: { title: "Theme", content: <CustomAppearancePanel /> }, 45 data: { title: "Data", content: <CustomDataPanel /> }, 46 settings: { title: "Settings", content: <CustomSettingsPanel /> } 47 } 48};
Custom Navigation Bar
Replace the default navigation bar with your own custom implementation using the navBar
prop:
Simple Custom Navigation
Create a minimal navigation bar with essential functionality:
tsx1import { useState } from 'react'; 2import { Home, Code, Eye } from 'lucide-react'; 3import { Button } from '@/components/ui/button'; 4import { useLayerStore } from '@/lib/ui-builder/store/layer-store'; 5import { useEditorStore } from '@/lib/ui-builder/store/editor-store'; 6import LayerRenderer from '@/components/ui/ui-builder/layer-renderer'; 7 8const SimpleNav = () => { 9 const [showCodeDialog, setShowCodeDialog] = useState(false); 10 const [showPreviewDialog, setShowPreviewDialog] = useState(false); 11 12 const selectedPageId = useLayerStore((state) => state.selectedPageId); 13 const findLayerById = useLayerStore((state) => state.findLayerById); 14 const componentRegistry = useEditorStore((state) => state.registry); 15 16 const page = findLayerById(selectedPageId); 17 18 return ( 19 <> 20 <div className="flex items-center justify-between bg-slate-50 px-4 py-2 border-b border-slate-200"> 21 <div className="flex items-center gap-2"> 22 <Home className="h-5 w-5 text-slate-600" /> 23 <span className="text-sm font-medium text-slate-800">UI Builder</span> 24 </div> 25 <div className="flex items-center gap-1"> 26 <Button 27 variant="ghost" 28 size="sm" 29 className="h-8 px-2 text-xs" 30 onClick={() => setShowCodeDialog(true)} 31 > 32 <Code className="h-3 w-3 mr-1" /> 33 Code 34 </Button> 35 <Button 36 variant="ghost" 37 size="sm" 38 className="h-8 px-2 text-xs" 39 onClick={() => setShowPreviewDialog(true)} 40 > 41 <Eye className="h-3 w-3 mr-1" /> 42 Preview 43 </Button> 44 </div> 45 </div> 46 47 {/* Simple Code Dialog */} 48 {showCodeDialog && ( 49 <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> 50 <div className="bg-white rounded-lg p-6 max-w-2xl max-h-[80vh] overflow-auto"> 51 <div className="flex justify-between items-center mb-4"> 52 <h2 className="text-lg font-semibold">Generated Code</h2> 53 <Button variant="ghost" size="sm" onClick={() => setShowCodeDialog(false)}> 54 × 55 </Button> 56 </div> 57 <div className="text-sm bg-gray-100 p-4 rounded overflow-auto"> 58 <pre>{`// Code export functionality would go here`}</pre> 59 </div> 60 </div> 61 </div> 62 )} 63 64 {/* Simple Preview Dialog */} 65 {showPreviewDialog && ( 66 <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> 67 <div className="bg-white rounded-lg p-6 max-w-4xl max-h-[80vh] overflow-auto"> 68 <div className="flex justify-between items-center mb-4"> 69 <h2 className="text-lg font-semibold">Page Preview</h2> 70 <Button variant="ghost" size="sm" onClick={() => setShowPreviewDialog(false)}> 71 × 72 </Button> 73 </div> 74 <div className="border rounded-lg p-4"> 75 {page && ( 76 <LayerRenderer 77 className="w-full" 78 page={page} 79 componentRegistry={componentRegistry} 80 /> 81 )} 82 </div> 83 </div> 84 </div> 85 )} 86 </> 87 ); 88}; 89 90// Use the custom navigation 91const customNavConfig = { 92 navBar: <SimpleNav />, 93 pageConfigPanelTabsContent: { 94 layers: { title: "Layers", content: defaultConfigTabsContent().layers.content }, 95 appearance: { title: "Appearance", content: defaultConfigTabsContent().appearance?.content }, 96 data: { title: "Data", content: defaultConfigTabsContent().data?.content } 97 } 98}; 99 100<UIBuilder 101 componentRegistry={myRegistry} 102 panelConfig={customNavConfig} 103/>
Advanced Custom Navigation
Build a more sophisticated navigation with additional features:
tsx1const AdvancedNav = () => { 2 const { pages, selectedPageId, selectPage, addPageLayer } = useLayerStore(); 3 const { previewMode, setPreviewMode } = useEditorStore(); 4 const [isPublishing, setIsPublishing] = useState(false); 5 6 const selectedPage = pages.find(page => page.id === selectedPageId); 7 8 const handlePublish = async () => { 9 setIsPublishing(true); 10 try { 11 // Your publish logic here 12 await publishPage(selectedPage); 13 } finally { 14 setIsPublishing(false); 15 } 16 }; 17 18 return ( 19 <div className="flex items-center justify-between bg-white px-6 py-3 border-b shadow-sm"> 20 {/* Left Section - Branding & Page Selector */} 21 <div className="flex items-center gap-4"> 22 <div className="flex items-center gap-2"> 23 <div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center"> 24 <span className="text-white text-sm font-bold">UB</span> 25 </div> 26 <h1 className="font-semibold text-lg">UI Builder Pro</h1> 27 </div> 28 29 <div className="h-6 w-px bg-gray-300" /> 30 31 <select 32 value={selectedPageId} 33 onChange={(e) => selectPage(e.target.value)} 34 className="border border-gray-300 rounded px-3 py-1 text-sm" 35 > 36 {pages.map(page => ( 37 <option key={page.id} value={page.id}> 38 {page.name} 39 </option> 40 ))} 41 </select> 42 43 <Button 44 variant="outline" 45 size="sm" 46 onClick={() => addPageLayer('New Page')} 47 > 48 <PlusIcon className="h-4 w-4 mr-1" /> 49 New Page 50 </Button> 51 </div> 52 53 {/* Center Section - Preview Mode Toggle */} 54 <div className="flex items-center gap-2"> 55 <span className="text-sm text-gray-600">Preview:</span> 56 <select 57 value={previewMode} 58 onChange={(e) => setPreviewMode(e.target.value)} 59 className="border border-gray-300 rounded px-2 py-1 text-sm" 60 > 61 <option value="desktop">Desktop</option> 62 <option value="tablet">Tablet</option> 63 <option value="mobile">Mobile</option> 64 <option value="responsive">Responsive</option> 65 </select> 66 </div> 67 68 {/* Right Section - Actions */} 69 <div className="flex items-center gap-2"> 70 <Button variant="outline" size="sm"> 71 <SaveIcon className="h-4 w-4 mr-1" /> 72 Save Draft 73 </Button> 74 75 <Button variant="outline" size="sm"> 76 <EyeIcon className="h-4 w-4 mr-1" /> 77 Preview 78 </Button> 79 80 <Button 81 variant="default" 82 size="sm" 83 onClick={handlePublish} 84 disabled={isPublishing} 85 > 86 {isPublishing ? ( 87 <Spinner className="h-4 w-4 mr-1" /> 88 ) : ( 89 <RocketIcon className="h-4 w-4 mr-1" /> 90 )} 91 {isPublishing ? 'Publishing...' : 'Publish'} 92 </Button> 93 </div> 94 </div> 95 ); 96};
PanelConfig API Reference
Type Definition
tsx1interface PanelConfig { 2 pageConfigPanelTabsContent?: TabsContentConfig; 3 navBar?: React.ReactNode; 4 editorPanel?: React.ReactNode; 5 propsPanel?: React.ReactNode; 6} 7 8interface TabsContentConfig { 9 layers: { title: string; content: React.ReactNode }; 10 appearance?: { title: string; content: React.ReactNode }; 11 data?: { title: string; content: React.ReactNode }; 12 [key: string]: { title: string; content: React.ReactNode } | undefined; 13}
Configuration Options
pageConfigPanelTabsContent
Customizes the left panel tabs:
- Required:
layers
- The component tree and page management tab - Optional:
appearance
- Theme and styling controls tab - Optional:
data
- Variable management tab - Custom tabs: Add your own tabs with any string key
tsx1// Example with all options 2const fullPanelConfig = { 3 pageConfigPanelTabsContent: { 4 layers: { title: "Structure", content: <LayersPanel /> }, 5 appearance: { title: "Design", content: <CustomAppearancePanel /> }, 6 data: { title: "Variables", content: <CustomDataPanel /> }, 7 settings: { title: "Settings", content: <SettingsPanel /> }, 8 integrations: { title: "APIs", content: <IntegrationsPanel /> } 9 } 10};
navBar
Customize the top navigation bar with your own React component:
tsx1const customNavBar = ( 2 <div className="flex items-center justify-between p-4 bg-white border-b"> 3 <div className="flex items-center gap-4"> 4 <Logo /> 5 <h1>My Custom Editor</h1> 6 </div> 7 <div className="flex items-center gap-2"> 8 <Button variant="outline">Preview</Button> 9 <Button>Publish</Button> 10 </div> 11 </div> 12); 13 14const panelConfig = { 15 navBar: customNavBar 16};
Common Use Cases
White-Label Applications
Customize the interface to match your brand:
tsx1const brandedPanelConfig = { 2 pageConfigPanelTabsContent: { 3 layers: { 4 title: "Components", 5 content: defaultConfigTabsContent().layers.content 6 }, 7 appearance: { 8 title: "Brand Kit", 9 content: <BrandKitPanel /> 10 }, 11 data: { 12 title: "Content", 13 content: <ContentManagementPanel /> 14 } 15 } 16};
Specialized Workflows
Tailor the interface for specific use cases:
tsx1// Email template builder 2const emailBuilderConfig = { 3 pageConfigPanelTabsContent: { 4 layers: { title: "Blocks", content: defaultConfigTabsContent().layers.content }, 5 appearance: { title: "Styling", content: <EmailStylePanel /> }, 6 data: { title: "Merge Tags", content: <MergeTagsPanel /> }, 7 preview: { title: "Preview", content: <EmailPreviewPanel /> } 8 } 9}; 10 11// Landing page builder 12const landingPageConfig = { 13 pageConfigPanelTabsContent: { 14 layers: { title: "Sections", content: defaultConfigTabsContent().layers.content }, 15 appearance: { title: "Theme", content: <ThemePanel /> }, 16 data: { title: "A/B Tests", content: <ABTestPanel /> }, 17 analytics: { title: "Analytics", content: <AnalyticsPanel /> } 18 } 19};
Simplified Interfaces
Hide complexity for non-technical users:
tsx1// Content editor - no technical options 2const contentEditorConfig = { 3 pageConfigPanelTabsContent: { 4 layers: { title: "Content", content: defaultConfigTabsContent().layers.content } 5 // Hide appearance and data tabs 6 } 7}; 8 9// Designer interface - focus on visual 10const designerConfig = { 11 pageConfigPanelTabsContent: { 12 layers: { title: "Layers", content: defaultConfigTabsContent().layers.content }, 13 appearance: { title: "Design", content: <AdvancedDesignPanel /> } 14 // Hide data/variables tab 15 } 16};
Integration with External Tools
Connect with your existing systems:
tsx1const integratedConfig = { 2 pageConfigPanelTabsContent: { 3 layers: { title: "Structure", content: defaultConfigTabsContent().layers.content }, 4 appearance: { title: "Styling", content: defaultConfigTabsContent().appearance?.content }, 5 data: { title: "Data", content: defaultConfigTabsContent().data?.content }, 6 cms: { title: "CMS", content: <CMSIntegrationPanel /> }, 7 assets: { title: "Assets", content: <AssetManagerPanel /> } 8 } 9};