Customizing the Configurator UI
Mimeeq provides two approaches for customizing the configurator UI, each suited for different levels of customization:
- Inline Custom CSS/JS (Simple) - Add small adjustments directly in embed templates
- Custom UI System (Advanced) - Create versioned UI packages with HTML, CSS, and JavaScript
Important: In both approaches, window.mimeeqApp
is already available when your code runs. No need to wait for any events or check for its existence.
Inline Custom CSS/JS (Simple Customizations)
For small adjustments like styling tweaks or simple modal logic, you can add custom code directly to your embed template:
How to Add Inline Code
- Go to your embed template in the admin panel
- Navigate to the bottom of the settings panel
- Add your custom CSS and/or JavaScript
- Save the template
Use Cases for Inline Customization
- Adjust element positioning or spacing
- Add simple click handlers or modal logic
- Hide/show elements based on conditions
- Small additions like some information applets
- Small style overrides
- Quick fixes and tweaks
- Translations
- PDF customization
Example: Simple Modal Logic
// Inline JS in embed template
// mimeeqApp is already available - no need to wait for any events
const closeBtn = document.createElement('button');
closeBtn.textContent = 'Close';
closeBtn.className = 'custom-close-btn';
closeBtn.onclick = () => {
document.getElementById('configurator-modal').style.display = 'none';
};
document.querySelector('.mmq-container').appendChild(closeBtn);
/* Inline CSS in embed template */
.custom-close-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
}
/* Adjust spacing */
.mmq-options-panel {
padding: 20px !important;
}
Custom UI System (Advanced Customizations)
For complex UI changes, structural modifications, or reusable components, use the Custom UI system:
Setting Up Custom UI
- Go to Settings > Embed Custom UIs
- Click New
- Give it a name (e.g., "Accordion Layout", "Industry Calculator")
- Create versions and upload your files
- Mark a version as "active"
- Select this Custom UI in your embed template
Custom UI Structure
Each Custom UI can contain:
- HTML template - Define new layouts and components which should be added in the body to append as a child of embed.
- CSS files - Complete styling systems
- JavaScript files - Complex functionality
- Images, fonts and other assets - Asset files you are going to use at your Custom UI.
- Multiple versions - Test changes without breaking production
How It Works
- Create a Custom UI in customer settings
- Add versions (e.g., "v1.0", "v2.0-beta")
- Upload your CSS, JS and asset files to each version
- Set one version as "active"
- In your embed template, select the Custom UI (and optionally a specific version)
Example: Complete Accordion UI
Set template at your Custom UI
HTML Template
<div class="custom-accordion-container">
<div id="accordion-root"></div>
<div id="product-summary" class="summary-panel"></div>
</div>
And create these files in your Custom UI:
accordion.js
class AccordionConfigurator {
constructor() {
this.blocks = [];
this.selectedOptions = {};
this.setup();
}
setup() {
// mimeeqApp is already available when Custom UI loads
// Subscribe to product structure
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
this.blocks = newValue;
this.render();
});
// Track selections
window.mimeeqApp.observers.optionSets.selectedOptions.subscribe(({ newValue }) => {
this.selectedOptions = {};
newValue.forEach(opt => {
this.selectedOptions[opt.blockId] = opt;
});
this.updateUI();
});
}
render() {
const root = document.getElementById('accordion-root');
root.innerHTML = '';
this.blocks.forEach((block, index) => {
const section = this.createAccordionSection(block, index);
root.appendChild(section);
});
}
createAccordionSection(block, index) {
const section = document.createElement('div');
section.className = 'accordion-section';
const header = document.createElement('div');
header.className = 'accordion-header';
header.innerHTML = `
<span>${block.name}</span>
<span class="accordion-value">${this.getSelectedValue(block)}</span>
`;
header.onclick = () => this.toggleSection(index);
const content = document.createElement('div');
content.className = 'accordion-content';
content.id = `section-${index}`;
content.style.display = index === 0 ? 'block' : 'none';
// Create option grid
const grid = document.createElement('div');
grid.className = 'option-grid';
block.options.forEach(option => {
const optionEl = this.createOption(option, block);
grid.appendChild(optionEl);
});
content.appendChild(grid);
section.appendChild(header);
section.appendChild(content);
return section;
}
createOption(option, block) {
const el = document.createElement('div');
el.className = 'option-item';
el.dataset.optionId = option.id;
if (option.image) {
el.innerHTML = `<img src="${option.image}" alt="${option.name}">`;
}
el.innerHTML += `<span>${option.name}</span>`;
el.onclick = () => {
window.mimeeqApp.actions.markOption(
option,
block.id,
'accordion',
true,
block.code
);
};
return el;
}
toggleSection(index) {
const content = document.getElementById(`section-${index}`);
const isOpen = content.style.display === 'block';
// Close all sections
document.querySelectorAll('.accordion-content').forEach(el => {
el.style.display = 'none';
});
// Open clicked section
if (!isOpen) {
content.style.display = 'block';
}
}
getSelectedValue(block) {
const selected = this.selectedOptions[block.id];
return selected ? selected.name : 'Select...';
}
updateUI() {
// Update accordion headers with selected values
this.blocks.forEach((block, index) => {
const header = document.querySelectorAll('.accordion-header')[index];
if (header) {
header.querySelector('.accordion-value').textContent = this.getSelectedValue(block);
}
});
// Update selected state on options
document.querySelectorAll('.option-item').forEach(el => {
const optionId = el.dataset.optionId;
const isSelected = Object.values(this.selectedOptions).some(opt => opt.id === optionId);
el.classList.toggle('selected', isSelected);
});
}
}
// Initialize
new AccordionConfigurator();
accordion.css
.custom-accordion-container {
display: flex;
gap: 30px;
height: 100%;
}
#accordion-root {
flex: 1;
max-width: 600px;
}
.accordion-section {
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 10px;
overflow: hidden;
}
.accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: #f8f8f8;
cursor: pointer;
font-weight: 600;
}
.accordion-header:hover {
background: #f0f0f0;
}
.accordion-value {
color: #666;
font-weight: 400;
}
.accordion-content {
padding: 20px;
border-top: 1px solid #e0e0e0;
}
.option-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 15px;
}
.option-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.option-item:hover {
border-color: #999;
transform: translateY(-2px);
}
.option-item.selected {
border-color: #000;
background: #f0f0f0;
}
.option-item img {
width: 60px;
height: 60px;
object-fit: contain;
margin-bottom: 8px;
}
.summary-panel {
width: 300px;
padding: 20px;
background: #f8f8f8;
border-radius: 8px;
}
Version Management
The Custom UI system supports multiple versions:
Custom UI: "Advanced Accordion"
├── Version 1.0 (active)
│ ├── accordion.js
│ └── accordion.css
├── Version 2.0-beta
│ ├── accordion.js
│ ├── accordion.css
│ └── utils.js
└── Version 1.1-bugfix
└── accordion.js
│ └── accordion.css
You can:
- Test new versions without affecting production
- Roll back to previous versions instantly
- A/B test different UI approaches
- Maintain different UIs for different products
Important Notes
About mimeeqApp Availability
When using either inline custom JS or the Custom UI system, window.mimeeqApp
is always available. Both are loaded as part of the embed initialization, so:
- No need to wait for
mimeeq-app-loaded
event - No need to check if
mimeeqApp
exists - You can immediately use all observers and actions
About HTML Templates
The HTML template field in Custom UI is for simple HTML snippets only:
- Keep templates concise (not thousands of lines)
- For complex HTML structures, create them dynamically via JavaScript
- The HTML is inserted as a child of the embed element
Choosing Between Inline and Custom UI System
Use Inline Custom CSS/JS When:
- Making small style adjustments
- Adding simple functionality
- Quick fixes or temporary changes
- Testing ideas before full implementation
Use Custom UI System When:
- Building complex interfaces
- Need version control
- Creating reusable components
- Implementing structural changes
- Managing multiple UI variations
More Examples
Example: Industry-Specific Calculator (Custom UI System)
For complex tools, create a Custom UI with these files:
HTML Template (simple snippet in template field)
<div id="calculator-root"></div>
calculator.js (creates the full UI dynamically)
// Create the calculator UI dynamically
const calculatorHTML = `
<div class="calculator-panel">
<h3>Room Coverage Calculator</h3>
<div class="calculator-inputs">
<label>
Room Width (m):
<input type="number" id="room-width" value="4">
</label>
<label>
Room Length (m):
<input type="number" id="room-length" value="5">
</label>
</div>
<div class="calculator-results">
<p>Room Area: <span id="room-area">20</span> m²</p>
<p>Units Needed: <span id="units-needed">-</span></p>
<p>Coverage: <span id="coverage">-</span> m²</p>
</div>
</div>
`;
document.getElementById('calculator-root').innerHTML = calculatorHTML;
class CoverageCalculator {
constructor() {
this.unitCoverage = 0;
this.currentQuantity = 1;
this.setupListeners();
this.subscribeToData();
}
setupListeners() {
['room-width', 'room-length'].forEach(id => {
document.getElementById(id).addEventListener('input', () => this.calculate());
});
}
subscribeToData() {
// mimeeqApp is already available
// Get coverage from product metadata
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue && newValue.metadata.coverage) {
this.unitCoverage = parseFloat(newValue.metadata.coverage);
this.calculate();
}
});
// Track quantity changes
window.mimeeqApp.observers.pricing.quantity.subscribe(({ newValue }) => {
this.currentQuantity = newValue || 1;
this.calculate();
});
}
calculate() {
const width = parseFloat(document.getElementById('room-width').value) || 0;
const length = parseFloat(document.getElementById('room-length').value) || 0;
const area = width * length;
document.getElementById('room-area').textContent = area.toFixed(1);
if (this.unitCoverage > 0) {
const unitsNeeded = Math.ceil(area / this.unitCoverage);
const totalCoverage = this.currentQuantity * this.unitCoverage;
document.getElementById('units-needed').textContent = unitsNeeded;
document.getElementById('coverage').textContent = totalCoverage.toFixed(1);
// Update quantity if needed
if (this.currentQuantity < unitsNeeded) {
window.mimeeqApp.actions.setQuantity(unitsNeeded);
}
}
}
}
new CoverageCalculator();
Example: Quick Style Fix (Inline CSS)
For simple adjustments, add this directly to your embed template:
/* Hide price for B2B customers */
.mmq-price-display {
display: none !important;
}
/* Make buttons larger on mobile */
@media (max-width: 768px) {
.mmq-button {
padding: 16px 24px !important;
font-size: 18px !important;
}
}
/* Custom loading animation */
.mmq-loader {
border-color: var(--brand-color) !important;
}
Best Practices
For Inline Custom Code
- Keep it simple and focused
- Use CSS custom properties when possible
- Avoid complex DOM manipulation
- Comment your code for future reference
For Custom UI System
- Create clear file structures
- Use semantic versioning (1.0, 1.1, 2.0)
- Document breaking changes between versions
- Test thoroughly before marking as active
General Best Practices
- mimeeqApp is always available in custom UI/JS - no need to check or wait
- Check if DOM elements exist before modifying
- Handle null/undefined observer values
- Unsubscribe from observers when removing elements
- Test on multiple devices and browsers
Common Patterns
Pattern: Progressive Enhancement
Start with inline CSS/JS, then move to Custom UI system as complexity grows:
// Start with inline JS
// Simple feature flag
if (window.enableAdvancedUI) {
document.body.classList.add('advanced-ui-enabled');
}
Later, create a full Custom UI package with the complete implementation.
Pattern: Defensive Coding
Always check if DOM elements exist before using them:
// Safe DOM manipulation
const container = document.querySelector('.mmq-container');
if (container) {
// Safe to use container
container.appendChild(myElement);
}
// Safe observer usage - check for data
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue && newValue.metadata) {
// Safe to use metadata
}
});
Pattern: Responsive Customization
Adapt your custom UI based on device:
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Load mobile-optimized UI
document.body.classList.add('custom-mobile-ui');
} else {
// Load desktop UI
document.body.classList.add('custom-desktop-ui');
}
Troubleshooting
Custom UI Not Loading
- Check that Custom UI is selected in embed template
- Verify files are uploaded to the active version
- Look for JavaScript errors in console
- Ensure your custom code doesn't have syntax errors
Styles Not Applying
- Check CSS specificity (you may need !important)
- Verify shadow DOM boundaries
- Ensure CSS file is uploaded correctly
- Check for syntax errors
JavaScript Errors
- Always check if DOM elements exist first
- Handle async data properly
- Use try-catch for external API calls
- Check browser compatibility
Next Steps
- CSS Variables - For pure visual customization
- CSS Parts - Style shadow DOM components
- API Reference - All available methods
- Observers Reference - All data streams
Choose the right approach for your needs - inline custom code for quick changes, or the Custom UI system for complex, versioned interfaces.