19 KiB
STYLE-003: Style Presets System
Overview
Create a library of style presets (Modern, Minimal, Playful, Enterprise) that users can choose when creating a new project. Each preset defines token values that cascade through the entire style system.
Phase: 8 (Styles Overhaul)
Priority: HIGH
Effort: 8-12 hours
Risk: Low
Dependencies: STYLE-001 (Token System), STYLE-002 (Element Configs)
Background
The Goal
When a user creates a new project, they should be able to:
- Choose a visual style preset from a gallery
- See live previews of what their UI will look like
- Start building immediately with consistent, professional styling
- Customize the preset later if desired
Why Presets Matter
- Beginners: Don't know what colors/spacing to choose
- Speed: Skip the "design token setup" phase
- Consistency: Entire project has cohesive look from start
- Exploration: Easy to try different aesthetics
Preset Definitions
Modern (Default)
A clean, professional look with subtle shadows and medium rounding. Works for most applications.
const ModernPreset: StylePreset = {
name: 'Modern',
description: 'Clean and professional with subtle depth',
tokens: {
// Colors
'--primary': '#3b82f6', // Blue
'--primary-hover': '#2563eb',
'--primary-foreground': '#ffffff',
'--secondary': '#64748b',
'--secondary-hover': '#475569',
'--secondary-foreground': '#ffffff',
'--destructive': '#ef4444',
'--destructive-hover': '#dc2626',
'--destructive-foreground': '#ffffff',
'--muted': '#f1f5f9',
'--muted-foreground': '#64748b',
'--accent': '#f1f5f9',
'--accent-foreground': '#0f172a',
'--background': '#ffffff',
'--foreground': '#0f172a',
'--surface': '#f8fafc',
'--border': '#e2e8f0',
'--border-subtle': '#f1f5f9',
'--ring': '#3b82f6',
// Typography
'--font-sans': 'Inter, ui-sans-serif, system-ui, sans-serif',
// Borders
'--radius-sm': '4px',
'--radius-md': '8px',
'--radius-lg': '12px',
// Shadows
'--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
'--shadow-md': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
},
preview: {
primaryButton: '#3b82f6',
cardBackground: '#ffffff',
cardBorder: '#e2e8f0',
textColor: '#0f172a',
radius: '8px',
}
};
Minimal
Ultra-clean with sharp corners, no shadows, and monochromatic palette.
const MinimalPreset: StylePreset = {
name: 'Minimal',
description: 'Ultra-clean with sharp edges and no shadows',
tokens: {
// Colors - Monochromatic
'--primary': '#18181b', // Near black
'--primary-hover': '#27272a',
'--primary-foreground': '#ffffff',
'--secondary': '#71717a',
'--secondary-hover': '#52525b',
'--secondary-foreground': '#ffffff',
'--destructive': '#dc2626',
'--destructive-hover': '#b91c1c',
'--destructive-foreground': '#ffffff',
'--muted': '#f4f4f5',
'--muted-foreground': '#71717a',
'--accent': '#f4f4f5',
'--accent-foreground': '#18181b',
'--background': '#ffffff',
'--foreground': '#18181b',
'--surface': '#fafafa',
'--border': '#e4e4e7',
'--border-subtle': '#f4f4f5',
'--ring': '#18181b',
// Typography
'--font-sans': 'system-ui, -apple-system, sans-serif',
// Borders - Sharp
'--radius-sm': '2px',
'--radius-md': '4px',
'--radius-lg': '6px',
// Shadows - None
'--shadow-sm': 'none',
'--shadow-md': 'none',
'--shadow-lg': 'none',
},
preview: {
primaryButton: '#18181b',
cardBackground: '#ffffff',
cardBorder: '#e4e4e7',
textColor: '#18181b',
radius: '4px',
}
};
Playful
Vibrant colors, generous rounding, and friendly typography.
const PlayfulPreset: StylePreset = {
name: 'Playful',
description: 'Vibrant colors and rounded, friendly shapes',
tokens: {
// Colors - Vibrant
'--primary': '#8b5cf6', // Purple
'--primary-hover': '#7c3aed',
'--primary-foreground': '#ffffff',
'--secondary': '#ec4899', // Pink
'--secondary-hover': '#db2777',
'--secondary-foreground': '#ffffff',
'--destructive': '#f43f5e', // Rose
'--destructive-hover': '#e11d48',
'--destructive-foreground': '#ffffff',
'--muted': '#faf5ff',
'--muted-foreground': '#6b7280',
'--accent': '#fce7f3',
'--accent-foreground': '#831843',
'--background': '#ffffff',
'--foreground': '#1e1b4b',
'--surface': '#faf5ff',
'--border': '#e9d5ff',
'--border-subtle': '#faf5ff',
'--ring': '#8b5cf6',
// Typography - Friendly
'--font-sans': '"Nunito", "Quicksand", ui-sans-serif, sans-serif',
// Borders - Very Rounded
'--radius-sm': '8px',
'--radius-md': '16px',
'--radius-lg': '24px',
'--radius-full': '9999px',
// Shadows - Soft colored
'--shadow-sm': '0 1px 3px rgb(139 92 246 / 0.1)',
'--shadow-md': '0 4px 12px rgb(139 92 246 / 0.15)',
},
preview: {
primaryButton: '#8b5cf6',
cardBackground: '#faf5ff',
cardBorder: '#e9d5ff',
textColor: '#1e1b4b',
radius: '16px',
}
};
Enterprise
Conservative, trustworthy palette with traditional styling.
const EnterprisePreset: StylePreset = {
name: 'Enterprise',
description: 'Professional and trustworthy for business applications',
tokens: {
// Colors - Conservative
'--primary': '#0f172a', // Dark navy
'--primary-hover': '#1e293b',
'--primary-foreground': '#ffffff',
'--secondary': '#475569',
'--secondary-hover': '#334155',
'--secondary-foreground': '#ffffff',
'--destructive': '#b91c1c',
'--destructive-hover': '#991b1b',
'--destructive-foreground': '#ffffff',
'--muted': '#f1f5f9',
'--muted-foreground': '#475569',
'--accent': '#e2e8f0',
'--accent-foreground': '#0f172a',
'--background': '#ffffff',
'--foreground': '#0f172a',
'--surface': '#f8fafc',
'--border': '#cbd5e1',
'--border-subtle': '#e2e8f0',
'--ring': '#0f172a',
// Typography - Traditional
'--font-sans': '"Source Sans Pro", "Segoe UI", ui-sans-serif, sans-serif',
// Borders - Conservative
'--radius-sm': '2px',
'--radius-md': '4px',
'--radius-lg': '6px',
// Shadows - Subtle
'--shadow-sm': '0 1px 2px rgb(0 0 0 / 0.05)',
'--shadow-md': '0 2px 4px rgb(0 0 0 / 0.06), 0 1px 2px rgb(0 0 0 / 0.04)',
},
preview: {
primaryButton: '#0f172a',
cardBackground: '#ffffff',
cardBorder: '#cbd5e1',
textColor: '#0f172a',
radius: '4px',
}
};
Soft
Gentle, calming aesthetic with soft colors and generous whitespace.
const SoftPreset: StylePreset = {
name: 'Soft',
description: 'Gentle colors and soft shapes for a calming aesthetic',
tokens: {
// Colors - Soft/Muted
'--primary': '#6366f1', // Indigo
'--primary-hover': '#4f46e5',
'--primary-foreground': '#ffffff',
'--secondary': '#a78bfa', // Light purple
'--secondary-hover': '#8b5cf6',
'--secondary-foreground': '#ffffff',
'--destructive': '#fb7185', // Soft red
'--destructive-hover': '#f43f5e',
'--destructive-foreground': '#ffffff',
'--muted': '#f5f3ff',
'--muted-foreground': '#6b7280',
'--accent': '#ede9fe',
'--accent-foreground': '#4c1d95',
'--background': '#fefefe',
'--foreground': '#374151',
'--surface': '#f9fafb',
'--border': '#e5e7eb',
'--border-subtle': '#f3f4f6',
'--ring': '#6366f1',
// Typography
'--font-sans': '"DM Sans", ui-sans-serif, system-ui, sans-serif',
// Borders - Soft rounded
'--radius-sm': '6px',
'--radius-md': '12px',
'--radius-lg': '20px',
// Shadows - Very soft
'--shadow-sm': '0 1px 3px rgb(0 0 0 / 0.02)',
'--shadow-md': '0 4px 6px rgb(0 0 0 / 0.04)',
},
preview: {
primaryButton: '#6366f1',
cardBackground: '#fefefe',
cardBorder: '#e5e7eb',
textColor: '#374151',
radius: '12px',
}
};
Implementation
Phase 1: Preset Data Structure (2-3 hrs)
Files to create:
packages/noodl-editor/src/editor/src/models/
├── StylePresets/
│ ├── StylePresetsModel.ts # Preset management
│ ├── presets/
│ │ ├── ModernPreset.ts
│ │ ├── MinimalPreset.ts
│ │ ├── PlayfulPreset.ts
│ │ ├── EnterprisePreset.ts
│ │ ├── SoftPreset.ts
│ │ └── index.ts
│ ├── StylePresetTypes.ts # TypeScript interfaces
│ └── index.ts
TypeScript Interfaces:
interface StylePreset {
id: string;
name: string;
description: string;
tokens: Record<string, string>;
preview: PresetPreview;
isBuiltIn: boolean;
createdAt?: Date;
}
interface PresetPreview {
primaryButton: string;
cardBackground: string;
cardBorder: string;
textColor: string;
radius: string;
}
interface StylePresetsModel {
builtInPresets: StylePreset[];
customPresets: StylePreset[];
getPreset(id: string): StylePreset | undefined;
applyPreset(presetId: string): void;
saveCustomPreset(preset: Omit<StylePreset, 'id' | 'isBuiltIn'>): StylePreset;
deleteCustomPreset(id: string): void;
exportPreset(id: string): string; // JSON
importPreset(json: string): StylePreset;
}
Phase 2: Preset Selector Component (3-4 hrs)
Files to create:
packages/noodl-core-ui/src/components/
├── StylePresets/
│ ├── PresetSelector.tsx # Main selector component
│ ├── PresetSelector.module.scss
│ ├── PresetSelector.stories.tsx
│ ├── PresetCard.tsx # Individual preset card
│ ├── PresetPreview.tsx # Live preview mini-UI
│ └── index.ts
PresetSelector UI:
┌─────────────────────────────────────────────────────────────────┐
│ STYLE PRESET │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Choose a visual style for your project: │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Modern │ │ Minimal │ │ Playful │ │ Corp │ │ Soft │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │
│ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │ │ btn │ │ │
│ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │
│ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │
│ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │ ─────── │ │
│ │ ███████ │ │ ███████ │ │ ███████ │ │ ███████ │ │ ███████ │ │
│ └────●────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ Selected │
│ │
│ "Clean and professional with subtle depth" │
│ │
│ [Customize After Creation] │
│ │
└─────────────────────────────────────────────────────────────────┘
PresetCard Component:
interface PresetCardProps {
preset: StylePreset;
isSelected: boolean;
onSelect: (presetId: string) => void;
}
function PresetCard({ preset, isSelected, onSelect }: PresetCardProps) {
return (
<div
className={cn(css.PresetCard, isSelected && css.Selected)}
onClick={() => onSelect(preset.id)}
>
<PresetPreview preview={preset.preview} />
<span className={css.PresetName}>{preset.name}</span>
</div>
);
}
Phase 3: Project Creation Integration (2-3 hrs)
Files to modify:
packages/noodl-editor/src/editor/src/views/
├── NewProjectDialog/
│ ├── NewProjectDialog.tsx # Add preset selector
│ └── NewProjectDialog.module.scss
packages/noodl-editor/src/editor/src/models/
├── ProjectModel.ts # Apply preset on creation
Integration Points:
// In NewProjectDialog.tsx
function NewProjectDialog() {
const [selectedPreset, setSelectedPreset] = useState('modern');
const handleCreate = async () => {
const project = await ProjectModel.create({
name: projectName,
// ... other options
});
// Apply selected preset's tokens
const preset = StylePresetsModel.getPreset(selectedPreset);
if (preset) {
StyleTokensModel.applyPreset(preset.tokens);
}
// Open project
EditorModel.openProject(project);
};
return (
<Dialog>
{/* ... name input ... */}
<PresetSelector
selected={selectedPreset}
onSelect={setSelectedPreset}
/>
{/* ... other options ... */}
<Button onClick={handleCreate}>Create Project</Button>
</Dialog>
);
}
Phase 4: Preset Management UI (2-3 hrs)
Files to create:
packages/noodl-editor/src/editor/src/views/panels/
├── PresetManagerPanel/
│ ├── PresetManagerPanel.tsx # Full preset management
│ ├── PresetManagerPanel.module.scss
│ ├── CreatePresetDialog.tsx
│ └── index.ts
Features:
- View all presets (built-in + custom)
- Create preset from current tokens
- Edit custom presets
- Delete custom presets
- Import/Export presets
Custom Preset Creation
From Project Settings
┌─────────────────────────────────────────────────────────────────┐
│ SAVE AS PRESET │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Save your current token configuration as a reusable preset. │
│ │
│ Preset Name: [My Brand Style ] │
│ Description: [Corporate brand colors] │
│ │
│ Preview: │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ ┌──────┐ │ │
│ │ │ btn │ Heading Text │ │
│ │ └──────┘ Body text preview with current styling │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ Card preview with surface color │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Save Preset] │
└─────────────────────────────────────────────────────────────────┘
Import/Export
// Export format (JSON)
{
"name": "My Brand Style",
"description": "Corporate brand colors",
"version": "1.0",
"tokens": {
"--primary": "#1a73e8",
"--primary-hover": "#1557b0",
// ... all token values
}
}
Testing Strategy
Unit Tests
- Preset loading and parsing
- Token application
- Custom preset CRUD operations
- Import/export JSON serialization
Integration Tests
- Preset selection in new project dialog
- Tokens apply correctly to elements
- Custom presets persist across sessions
Manual Testing Checklist
- Create project with each built-in preset
- Verify elements styled correctly per preset
- Create custom preset from current tokens
- Import preset from JSON
- Export preset to JSON
- Delete custom preset
- Preset preview matches actual result
Success Criteria
- 5 built-in presets available
- Preset selector in project creation dialog
- Live preview in preset cards
- Custom preset creation works
- Import/export functionality
- Presets correctly populate all tokens
Future Considerations
Dark Mode Variants
Each preset could have light/dark variants:
interface StylePreset {
// ...
modes: {
light: Record<string, string>;
dark: Record<string, string>;
};
}
Community Presets
Allow sharing presets via:
- Export as JSON file
- Copy/paste shareable code
- (Future) Community preset gallery
Preset Inheritance
Allow presets to extend other presets:
const MyPreset = {
extends: 'modern',
tokens: {
'--primary': '#custom-color', // Override just this
// Other tokens inherited from 'modern'
}
};
Dependencies
Blocked By:
- STYLE-001 (Token System) - presets populate tokens
- STYLE-002 (Element Configs) - tokens flow to variants
Blocks:
- WIZARD-001 (Project Wizard) - uses preset selector
Last Updated: January 2026