Skip to content
board-game-ui logo

Board Game UI Skill

board-game-ui

UI/UX design for digital board games. Use when building game interfaces, implementing drag-and-drop, rendering game boards, showing player information, handling animations, or designing responsive layouts. Covers Canvas, SVG, and DOM-based approaches.

SKILL.md

Full skill instructions

Board Game UI Skill

Overview

This skill provides expertise for building user interfaces for digital board games. It covers rendering approaches (DOM, Canvas, SVG), interaction patterns (drag-and-drop, click-to-select), responsive design for different screen sizes, and UX principles specific to turn-based games.

Rendering Approaches

When to Use Each

ApproachBest ForAvoid When
DOM/​CSSCard games, simple boards, UI overlaysMany moving pieces, complex animations
SVGMaps, vector graphics, zoomable boardsThousands of elements, pixel effects
CanvasComplex animations, particles, real-timeAccessibility needed, text-heavy
HybridMost board gamesOver-engineering simple games

Recommended: Hybrid Approach

Use DOM for UI chrome (menus, player info, cards) and Canvas/​SVG for the game board:

<div class="game-container">
  <!-- DOM: Player info, always visible -->
  <aside class="player-panel">
    <div class="player-info" data-player="1">...</​div>
  </​aside>

  <!-- SVG: The game board/​map -->
  <main class="board-area">
    <svg id="game-board" viewBox="0 0 1000 800">
      <!-- Routes, cities, tokens -->
    </​svg>
  </​main>

  <!-- DOM: Action buttons, cards in hand -->
  <footer class="action-bar">
    <div class="hand-cards">...</​div>
    <div class="action-buttons">...</​div>
  </​footer>
</​div>

Layout Patterns

Responsive Game Layout

.game-container {
  display: grid;
  height: 100vh;
  gap: 1rem;
  padding: 1rem;

  /​* Desktop: sidebar layout */
  grid-template-columns: 250px 1fr;
  grid-template-rows: 1fr auto;
  grid-template-areas:
    "sidebar board"
    "sidebar actions";
}

/​* Tablet: stack sidebar above */
@media (max-width: 1024px) {
  .game-container {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
      "sidebar"
      "board"
      "actions";
  }

  .player-panel {
    display: flex;
    overflow-x: auto;
  }
}

/​* Mobile: minimal chrome */
@media (max-width: 600px) {
  .game-container {
    padding: 0.5rem;
    gap: 0.5rem;
  }

  .player-panel {
    font-size: 0.875rem;
  }
}

Aspect Ratio Preservation

.board-area {
  grid-area: board;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

#game-board {
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 5 / 4;  /​* Match your board dimensions */
}

SVG Game Board

Board Structure

<svg id="game-board" viewBox="0 0 1000 800">
  <!-- Background layer -->
  <g id="background">
    <image href="/​assets/​map-age-1.jpg" width="1000" height="800" />
  </​g>

  <!-- Routes layer -->
  <g id="routes">
    <path class="route" data-route-id="london-paris"
          d="M 200,150 Q 250,200 350,180"
          stroke="#666" stroke-width="8" fill="none" />
  </​g>

  <!-- Cities layer -->
  <g id="cities">
    <g class="city" data-city-id="london" transform="translate(200, 150)">
      <circle r="20" fill="#333" />
      <text y="35" text-anchor="middle">London</​text>
    </​g>
  </​g>

  <!-- Tokens layer (on top) -->
  <g id="tokens">
    <g class="ship-token" data-player="1" data-ship-id="ship-1"
       transform="translate(200, 150)">
      <use href="#airship-icon" fill="var(--player-1-color)" />
    </​g>
  </​g>

  <!-- Definitions -->
  <defs>
    <symbol id="airship-icon" viewBox="0 0 40 20">
      <ellipse cx="20" cy="10" rx="18" ry="8" />
      <rect x="8" y="14" width="24" height="4" rx="2" />
    </​symbol>
  </​defs>
</​svg>

Interactive Elements

// Click handling on SVG elements
document.getElementById('game-board').addEventListener('click', (e) => {
  const route = e.target.closest('.route');
  const city = e.target.closest('.city');
  const token = e.target.closest('.ship-token');

  if (route) {
    handleRouteClick(route.dataset.routeId);
  } else if (city) {
    handleCityClick(city.dataset.cityId);
  } else if (token) {
    handleTokenClick(token.dataset.shipId);
  }
});

// Hover effects
function setupHoverEffects() {
  document.querySelectorAll('.route').forEach(route => {
    route.addEventListener('mouseenter', () => {
      route.classList.add('highlighted');
      showRouteTooltip(route.dataset.routeId);
    });
    route.addEventListener('mouseleave', () => {
      route.classList.remove('highlighted');
      hideTooltip();
    });
  });
}
/​* SVG hover/​selection states */
.route {
  cursor: pointer;
  transition: stroke 0.2s, stroke-width 0.2s;
}

.route:hover,
.route.highlighted {
  stroke: #4a9eff;
  stroke-width: 12;
}

.route.claimed {
  stroke: var(--claiming-player-color);
}

.route.unavailable {
  opacity: 0.3;
  pointer-events: none;
}

.city:hover circle {
  fill: #555;
  transform: scale(1.1);
}

Drag and Drop

HTML5 Drag and Drop (for cards/​tiles)

// Make elements draggable
function setupDraggable(element, type, id) {
  element.draggable = true;

  element.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('application/​json', JSON.stringify({ type, id }));
    e.dataTransfer.effectAllowed = 'move';
    element.classList.add('dragging');
  });

  element.addEventListener('dragend', () => {
    element.classList.remove('dragging');
    clearDropTargets();
  });
}

// Make slots accept drops
function setupDropTarget(element, acceptTypes, onDrop) {
  element.addEventListener('dragover', (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    element.classList.add('drop-target');
  });

  element.addEventListener('dragleave', () => {
    element.classList.remove('drop-target');
  });

  element.addEventListener('drop', (e) => {
    e.preventDefault();
    element.classList.remove('drop-target');

    const data = JSON.parse(e.dataTransfer.getData('application/​json'));
    if (acceptTypes.includes(data.type)) {
      onDrop(data);
    }
  });
}

Click-to-Select Alternative

For touch devices and accessibility:

let selectedItem = null;

function handleItemClick(item) {
  if (selectedItem === item) {
    // Deselect
    deselectItem();
  } else if (selectedItem) {
    // Try to place selected item
    if (canPlaceAt(selectedItem, item)) {
      placeItem(selectedItem, item);
    }
    deselectItem();
  } else {
    // Select this item
    selectItem(item);
  }
}

function selectItem(item) {
  selectedItem = item;
  item.classList.add('selected');
  highlightValidTargets(item);
}

function deselectItem() {
  if (selectedItem) {
    selectedItem.classList.remove('selected');
    clearHighlights();
    selectedItem = null;
  }
}

Player Board UI

Blueprint Slot Grid

<div class="blueprint">
  <div class="blueprint-grid">
    <!-- Frame slots -->
    <div class="slot frame-slot" data-slot-type="frame" data-slot-id="frame-1">
      <div class="slot-label">Frame</​div>
      <div class="slot-content">
        <!-- Upgrade tile goes here when installed -->
      </​div>
      <div class="gas-socket">
        <!-- Gas cube indicator -->
      </​div>
    </​div>

    <!-- Drive slots -->
    <div class="slot drive-slot" data-slot-type="drive" data-slot-id="drive-1">
      <div class="slot-label">Drive</​div>
      <div class="slot-content empty"></​div>
    </​div>

    <!-- More slots... -->
  </​div>

  <div class="blueprint-stats">
    <div class="stat">
      <span class="stat-icon">⚖️</​span>
      <span class="stat-value" data-stat="weight">12</​span>
    </​div>
    <div class="stat">
      <span class="stat-icon">🎈</​span>
      <span class="stat-value" data-stat="lift">15</​span>
    </​div>
    <!-- More stats -->
  </​div>
</​div>
.blueprint-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
  padding: 1rem;
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
  border-radius: 8px;
}

.slot {
  aspect-ratio: 1;
  border: 2px dashed #444;
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s, background 0.2s;
}

.slot.empty:hover {
  border-color: #4a9eff;
  background: rgba(74, 158, 255, 0.1);
}

.slot.filled {
  border-style: solid;
  border-color: #666;
}

.slot.drop-target {
  border-color: #4aff4a;
  background: rgba(74, 255, 74, 0.1);
}

Card Hand Display

<div class="hand-container">
  <div class="hand" id="player-hand">
    <!-- Cards fan out -->
  </​div>
</​div>
.hand-container {
  perspective: 1000px;
  padding: 1rem;
}

.hand {
  display: flex;
  justify-content: center;
}

.card {
  width: 120px;
  height: 180px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
  margin-left: -40px;  /​* Overlap */
  transition: transform 0.2s, margin 0.2s;
  cursor: pointer;
}

.card:first-child {
  margin-left: 0;
}

.card:hover {
  transform: translateY(-20px) scale(1.05);
  margin-left: 0;
  margin-right: 40px;
  z-index: 10;
}

.card.selected {
  transform: translateY(-30px);
  box-shadow: 0 0 20px rgba(74, 158, 255, 0.5);
}

Animations

Token Movement

function animateTokenMove(tokenElement, fromPos, toPos, duration = 500) {
  return new Promise(resolve => {
    // Calculate path
    const dx = toPos.x - fromPos.x;
    const dy = toPos.y - fromPos.y;

    // Use CSS animation
    tokenElement.style.transition = `transform ${duration}ms ease-out`;
    tokenElement.style.transform = `translate(${dx}px, ${dy}px)`;

    setTimeout(() => {
      // After animation, update actual position
      tokenElement.style.transition = '';
      tokenElement.style.transform = '';
      tokenElement.setAttribute('transform', `translate(${toPos.x}, ${toPos.y})`);
      resolve();
    }, duration);
  });
}

State Change Animations

/​* Highlight changes */
@keyframes pulse-highlight {
  0%, 100% { box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.5); }
  50% { box-shadow: 0 0 0 10px rgba(74, 158, 255, 0); }
}

.stat-value.changed {
  animation: pulse-highlight 0.5s ease-out;
}

/​* Income animation */
@keyframes float-up {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-30px); }
}

.income-popup {
  animation: float-up 1s ease-out forwards;
  color: #4aff4a;
  font-weight: bold;
}

Turn Indicator

<div class="turn-indicator">
  <div class="current-player">
    <div class="player-avatar" style="--player-color: var(--player-1-color)"></​div>
    <span class="player-name">Germany</​span>
  </​div>
  <div class="turn-timer" id="turn-timer">
    <svg class="timer-ring" viewBox="0 0 36 36">
      <circle cx="18" cy="18" r="16" fill="none" stroke="#333" stroke-width="3" />
      <circle class="timer-progress" cx="18" cy="18" r="16"
              fill="none" stroke="#4a9eff" stroke-width="3"
              stroke-dasharray="100" stroke-dashoffset="0" />
    </​svg>
    <span class="timer-text">60</​span>
  </​div>
</​div>
function startTurnTimer(duration) {
  const timerText = document.querySelector('.timer-text');
  const timerProgress = document.querySelector('.timer-progress');
  let remaining = duration;

  const interval = setInterval(() => {
    remaining--;
    timerText.textContent = remaining;

    const percent = (remaining / duration) * 100;
    timerProgress.style.strokeDashoffset = 100 - percent;

    if (remaining <= 0) {
      clearInterval(interval);
    }
  }, 1000);

  return () => clearInterval(interval);
}

Tooltips and Info Panels

// Tooltip system
const tooltip = document.getElementById('tooltip');

function showTooltip(content, x, y) {
  tooltip.innerHTML = content;
  tooltip.style.left = `${x + 10}px`;
  tooltip.style.top = `${y + 10}px`;
  tooltip.classList.add('visible');
}

function hideTooltip() {
  tooltip.classList.remove('visible');
}

// Rich tooltips for game elements
function getTechnologyTooltip(techId) {
  const tech = technologies[techId];
  return `
    <div class="tooltip-tech">
      <h4>${tech.name}</​h4>
      <p class="tooltip-cost">Cost: ${tech.cost} Research</​p>
      <p class="tooltip-desc">${tech.description}</​p>
      <p class="tooltip-unlocks">Unlocks: ${tech.unlocks}</​p>
    </​div>
  `;
}

Accessibility

Keyboard Navigation

// Make game elements keyboard accessible
document.querySelectorAll('.slot, .card, .route').forEach(el => {
  el.setAttribute('tabindex', '0');
  el.setAttribute('role', 'button');

  el.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      el.click();
    }
  });
});

Screen Reader Support

<!-- Announce game state changes -->
<div id="game-announcer" aria-live="polite" class="sr-only"></​div>

<script>
function announce(message) {
  document.getElementById('game-announcer').textContent = message;
}

// Usage
announce("Germany's turn. They placed a worker at the Construction Hall.");
</​script>

Performance Tips

  1. Batch DOM updates - Use DocumentFragment or requestAnimationFrame
  2. Virtual scrolling - For long lists (action log, card library)
  3. Lazy load assets - Load board images for other Ages on demand
  4. Debounce resize handlers - Don't recalculate layout on every pixel
  5. Use CSS transforms - For animations instead of top/​left
  6. Layer with z-index - Keep frequently-updated elements on separate layers

When This Skill Activates

Use this skill when:

  • Building game board rendering
  • Implementing drag-and-drop interactions
  • Designing player board layouts
  • Creating card hand displays
  • Adding animations and transitions
  • Making responsive game layouts
  • Improving game accessibility

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View