Skip to content

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

c
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:

  1. Drag widgets onto a canvas
  2. See your layout in real-time
  3. Export code with clean, consistent coordinates
  4. Switch display sizes without redoing everything

If you must code by hand, use relative positioning:

c
// 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:

  1. Primary information — Large, prominent, center screen (e.g., temperature)
  2. Secondary information — Medium size, supporting context (e.g., humidity)
  3. Tertiary information — Small, peripheral (e.g., timestamp, battery)
c
// 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

c
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)

c
// 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:

c
// 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:

  1. Flash a basic test screen on your first day with the display
  2. Verify colors, fonts, and touch calibration immediately
  3. Iterate on hardware, not just in the simulator
  4. 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

MistakeImpactFix
Hardcoded coordinatesSlow, fragile layoutsUse visual builder or relative alignment
No visual hierarchyConfusing interfaceSize/color by importance
Small touch targetsFrustrating UXMinimum 40×40px buttons
No user feedbackUncertain interactionsPress states, spinners, toasts
Late hardware testingExpensive reworkTest on hardware from day one

Resources

What GUI mistakes have you made? Share your war stories in the comments — we've all been there.