5 Common Embedded GUI Mistakes (And How to Avoid Them)
Published on Lopaka.app — The free web-based LVGL GUI builder for embedded developers
Introduction
I've reviewed hundreds of embedded GUI projects — from hobbyist Arduino builds to production IoT devices. And I keep seeing the same mistakes over and over.
These aren't minor nitpicks. They're the difference between an interface that feels professional and one that makes users reach for the serial monitor instead.
In this post, I'll walk through the 5 most common embedded GUI mistakes I see, why they happen, and how to avoid them — often in just a few minutes.
Mistake #1: Hardcoding Pixel Coordinates
The Problem
lv_obj_set_pos(label1, 47, 83);
lv_obj_set_pos(btn1, 112, 156);
lv_obj_set_pos(bar1, 23, 201);You position every element by guessing pixel coordinates, then adjust by ±5 pixels, re-flash, check, repeat. It's slow, fragile, and breaks the moment you change display sizes.
Why It Happens
Most Arduino GUI tutorials show you how to create widgets, but not how to lay them out. So you do the math yourself — and the math is tedious.
The Fix
Use a visual GUI builder like Lopaka that handles positioning for you:
- Drag widgets onto a canvas
- See your layout in real-time
- Export code with clean, consistent coordinates
- Switch display sizes without redoing everything
If you must code by hand, use relative positioning:
// Instead of hardcoded positions:
lv_obj_align(label, LV_ALIGN_CENTER, 0, -40);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 20);
lv_obj_align(bar, LV_ALIGN_BOTTOM_MID, 0, -20);Mistake #2: No Visual Hierarchy
The Problem
Everything on the screen is the same size, same color, same visual weight. The user doesn't know where to look first.
┌─────────────────────────────┐
│ Temperature: 25.6C │
│ Humidity: 65% │
│ Pressure: 1013 hPa │
│ Battery: 75% │
│ Status: OK │
│ Time: 14:32 │
│ Date: 2026-04-02 │
└─────────────────────────────┘This is a data dump, not an interface.
Why It Happens
Embedded developers think in data structures, not visual design. Every sensor reading gets equal treatment because every variable is equally important in the code.
The Fix
Establish a clear hierarchy:
- Primary information — Large, prominent, center screen (e.g., temperature)
- Secondary information — Medium size, supporting context (e.g., humidity)
- Tertiary information — Small, peripheral (e.g., timestamp, battery)
// Primary — big and bold
lv_obj_set_style_text_font(temp_label, &lv_font_montserrat_36, 0);
// Secondary — readable but smaller
lv_obj_set_style_text_font(humidity_label, &lv_font_montserrat_20, 0);
// Tertiary — subtle
lv_obj_set_style_text_font(time_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(time_label, lv_color_hex(0x888888), 0);Good hierarchy:
┌─────────────────────────────┐
│ │
│ 25.6°C │ ← Primary (large, center)
│ │
│ Humidity: 65% │ ← Secondary (medium)
│ Pressure: 1013 hPa │
│ │
│ 🔋 75% 14:32 │ ← Tertiary (small, bottom)
└─────────────────────────────┘Mistake #3: Ignoring Touch Target Sizes
The Problem
lv_obj_set_size(tiny_button, 40, 20);A button that's 40×20 pixels on a 2.8" resistive touchscreen is a nightmare to press accurately. Users will miss, get frustrated, and stop using your interface.
Why It Happens
Developers design for the display, not for the finger. On a 320×240 screen, 40 pixels looks reasonable — until you try to tap it with a fingertip.
The Fix
Minimum touch target: 40×40 pixels (ideally larger)
// Too small
lv_obj_set_size(btn, 40, 20);
// Good
lv_obj_set_size(btn, 80, 44);
// Better — with padding
lv_obj_set_size(btn, 100, 50);
lv_obj_set_style_pad_all(btn, 8, 0);Rules of thumb:
- Buttons: minimum 44px height (Apple's HIG recommendation)
- Spacing between buttons: at least 8px
- If you can't fit the text comfortably, the button is too small
- Test with your actual hardware — resistive touchscreens need larger targets than capacitive
Mistake #4: No Feedback for User Actions
The Problem
User presses a button. Nothing appears to happen. They press it again. And again. Now the action has fired three times.
Why It Happens
Embedded systems are slow. The gap between touch and visual feedback can be 100-500ms — an eternity in UI time. Without immediate feedback, users assume the touch didn't register.
The Fix
Provide instant visual feedback:
// Button press state change
lv_obj_set_style_bg_color(btn, lv_color_hex(0x444444), LV_STATE_PRESSED);
lv_obj_set_style_transform_scale(btn, 240, LV_STATE_PRESSED);
// Loading indicator for slow operations
lv_obj_t *spinner = lv_spinner_create(lv_scr_act(), 1000, 60);
lv_obj_center(spinner);
// Toast notification for completed actions
lv_obj_t *toast = lv_label_create(lv_scr_act());
lv_label_set_text(toast, "Saved!");
lv_obj_align(toast, LV_ALIGN_BOTTOM_MID, 0, -10);
lv_obj_fade_out(toast, 500, 1500);Feedback checklist:
- Button changes appearance when pressed
- Loading spinner for operations >200ms
- Confirmation message for destructive actions
- Error state visible and clear (red border, error icon)
- Success confirmation for completed actions
Mistake #5: Not Testing on Real Hardware Early
The Problem
Your GUI looks perfect in the simulator. You flash it to hardware and discover:
- Colors look washed out on the actual display
- Text is unreadable at the chosen size
- Touch calibration is off by 10 pixels
- The display is slower than expected, causing visible redraw lag
Why It Happens
Simulators are convenient. Hardware testing requires setup. So developers defer hardware testing until the GUI is "done" — and then discover everything is wrong.
The Fix
Test on real hardware from day one:
- Flash a basic test screen on your first day with the display
- Verify colors, fonts, and touch calibration immediately
- Iterate on hardware, not just in the simulator
- Use Lopaka's live preview to design, then flash incrementally
Quick hardware sanity checklist:
- Display driver initialized correctly
- Colors match expectations (test with color swatches)
- Font sizes are readable at normal viewing distance
- Touch calibration accurate (test corners and center)
- Refresh rate acceptable (no visible flicker or tearing)
- Power consumption within budget (displays are power-hungry)
Bonus: The Meta-Mistake
Not using tools that exist to solve these problems.
You don't need to hand-code every pixel position. You don't need to guess at color schemes. You don't need to manually calculate touch target sizes.
Tools like Lopaka exist specifically to eliminate these mistakes:
- Visual layout → No more hardcoded coordinates
- Built-in style guides → Consistent visual hierarchy
- Touch-aware sizing → Buttons are always finger-friendly
- Real-time preview → See changes instantly, test on hardware incrementally
- Clean code export → Production-ready LVGL code, no black-box runtime
Summary
| Mistake | Impact | Fix |
|---|---|---|
| Hardcoded coordinates | Slow, fragile layouts | Use visual builder or relative alignment |
| No visual hierarchy | Confusing interface | Size/color by importance |
| Small touch targets | Frustrating UX | Minimum 40×40px buttons |
| No user feedback | Uncertain interactions | Press states, spinners, toasts |
| Late hardware testing | Expensive rework | Test on hardware from day one |
Resources
- Lopaka.app — Build GUIs without these mistakes
- LVGL Style System — Deep dive into LVGL styling
- Apple HIG Touch Targets — Industry-standard sizing
- Lopaka Discord — Get help from the community
What GUI mistakes have you made? Share your war stories in the comments — we've all been there.