🌾 FarmHeart
← Back to Blog

UI Design for Browser Games: HUDs, Menus, and Responsive Layouts

October 6, 2026 · FarmHeart · 10 min read

The Two Worlds of Game UI

Browser games have a unique advantage: you can use both HTML/CSS and canvas rendering for your interface. Most native game engines force you to build UI inside the game renderer. In the browser, you get the full power of CSS — flexbox, grid, transitions, media queries — layered on top of your canvas.

The question isn’t which one to use. It’s which one to use for what.

When to Use HTML/CSS Overlay

When to Use Canvas-Rendered UI

HUD Design Patterns

A good HUD shows only what the player needs, when they need it. Here are patterns that work well in browser games:

The Corner Layout

Place persistent info in screen corners using CSS position: fixed:

<div id="hud" style="position:fixed;inset:0;pointer-events:none;z-index:10">
  <div class="hud-tl">Health / Mana</div>
  <div class="hud-tr">Minimap</div>
  <div class="hud-bl">Hotbar / Tools</div>
  <div class="hud-br">Gold / Day counter</div>
</div>

<style>
.hud-tl { position:absolute; top:12px; left:12px; }
.hud-tr { position:absolute; top:12px; right:12px; }
.hud-bl { position:absolute; bottom:12px; left:12px; }
.hud-br { position:absolute; bottom:12px; right:12px; }
[class^="hud-"] { pointer-events:auto; }
</style>

The pointer-events: none on the container lets clicks pass through to the game canvas, while pointer-events: auto on the actual HUD elements makes them interactive.

Health and Resource Bars

For a farming game, resource bars might show energy, hydration, or crop health:

.bar-container {
  width: 120px; height: 14px;
  background: rgba(0,0,0,.5);
  border-radius: 7px;
  overflow: hidden;
}
.bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #e74c3c, #f39c12);
  transition: width 0.3s ease;
  border-radius: 7px;
}
/* Update via JS: barFill.style.width = `${hp/maxHp*100}%` */

Context-Sensitive HUD

Don’t show everything at once. Show the watering can tooltip only when hovering over a crop, the sell price only when near the shipping bin, the build menu only in build mode. Use a state machine to toggle HUD panels:

function updateHUD(gameState) {
  document.getElementById('build-panel').hidden = gameState.mode !== 'build';
  document.getElementById('shop-panel').hidden  = !gameState.nearShop;
  document.getElementById('tool-tip').hidden    = !gameState.hoveredEntity;
}

Menu Systems

Layered Menu Stack

Games need nested menus: Main Menu → Settings → Audio. Use a stack pattern:

const menuStack = [];

function pushMenu(menuId) {
  if (menuStack.length) {
    document.getElementById(menuStack.at(-1)).hidden = true;
  }
  menuStack.push(menuId);
  document.getElementById(menuId).hidden = false;
}

function popMenu() {
  const current = menuStack.pop();
  document.getElementById(current).hidden = true;
  if (menuStack.length) {
    document.getElementById(menuStack.at(-1)).hidden = false;
  }
}

// Escape key pops the stack
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && menuStack.length) popMenu();
});

Pause Menu

When the pause menu opens, stop the game loop but keep the last frame visible behind a semi-transparent overlay:

#pause-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.7);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

Responsive Game UI

Your game runs on 27-inch monitors and 5-inch phones. The canvas scales, but the UI needs to adapt:

Rule of thumb: UI elements should be at least 44×44 CSS pixels for touch targets on mobile. A button that’s fine with a mouse cursor will be frustratingly small on a phone.
/* Desktop: multi-column inventory */
.inventory-grid {
  display: grid;
  grid-template-columns: repeat(8, 48px);
  gap: 4px;
}

/* Mobile: fewer columns, bigger slots */
@media (max-width: 600px) {
  .inventory-grid {
    grid-template-columns: repeat(5, 56px);
    gap: 6px;
  }
  .hud-tl, .hud-tr, .hud-bl, .hud-br {
    transform: scale(1.25);
    transform-origin: inherit;
  }
}

Tooltips and Dialogs

Tooltips in games need to follow the cursor (or touch point) and stay within viewport bounds:

function positionTooltip(tooltip, x, y) {
  const pad = 12;
  const rect = tooltip.getBoundingClientRect();
  let left = x + pad;
  let top = y + pad;

  // Clamp to viewport
  if (left + rect.width > window.innerWidth) left = x - rect.width - pad;
  if (top + rect.height > window.innerHeight) top = y - rect.height - pad;

  tooltip.style.left = `${left}px`;
  tooltip.style.top = `${top}px`;
}

For touch devices, show tooltips on long-press rather than hover. A 300ms press-and-hold is the standard mobile convention.

Typography for Games

Font choice affects both feel and performance:

Always use font-display: swap in your @font-face to prevent invisible text while the font loads.

Accessibility in Game UI

Game UI is notoriously inaccessible. Small changes make a big difference:

UI ElementBest ApproachWhy
Main menuHTML/CSSText, buttons, accessibility
In-game HUDHTML overlayEasy to update, CSS transitions
Floating health barsCanvasMoves with game camera
MinimapCanvasRedraws every frame
Inventory gridHTML/CSSDrag-and-drop, responsive layout
Dialog/speech bubblesHTML overlayRich text, auto-sizing

See Game UI in Action

FarmHeart uses all of these patterns. Open DevTools and inspect the HUD.

Play FarmHeart