Skip to content

πŸ“Š One-Screen Board Dashboard

Design Philosophy

Board directors have 2-3 minutes to:

  1. Understand current state
  2. Identify what needs attention
  3. Make decisions
  4. Delegate actions

The one-screen dashboard delivers this without scrolling, clicking, or searching.

Information Hierarchy

Level 1: Overall Health (5 seconds)

Single metric or visualization showing overall compliance/security posture

Level 2: Key Metrics (15 seconds)

3-5 critical metrics that matter to the board

Level 3: Decisions Required (30 seconds)

Actions needing board input, clearly prioritized

Supporting information for decision-making

Dashboard Layout

<div class="gc-board-dashboard">
  <!-- Fixed Header -->
  <header class="gc-board-header">
    <div class="gc-board-header__brand">
      <h1>GetCimple Board View</h1>
    </div>
    <div class="gc-board-header__period">
      <select aria-label="Reporting period">
        <option>Q4 2024</option>
        <option selected>Q1 2025</option>
      </select>
    </div>
    <div class="gc-board-header__actions">
      <button class="gc-board-header__export">Export Report</button>
      <button class="gc-board-header__settings">Settings</button>
    </div>
  </header>

  <!-- Main Dashboard Grid -->
  <main class="gc-board-main">
    <!-- Hero Metric -->
    <section class="gc-board-hero" aria-label="Overall compliance status">
      <div class="gc-board-hero__content">
        <div class="gc-board-hero__metric">
          <span class="gc-board-hero__value">85%</span>
          <span class="gc-board-hero__label">Overall Compliance</span>
        </div>
        <div class="gc-board-hero__trend">
          <svg class="gc-board-hero__arrow gc-board-hero__arrow--up">
            <use href="#icon-trend-up"></use>
          </svg>
          <span>+5% from last quarter</span>
        </div>
        <div class="gc-board-hero__benchmark">Industry Average: 78%</div>
      </div>
      <div class="gc-board-hero__visual">
        <!-- Circular progress or gauge -->
        <svg class="gc-board-hero__gauge" viewBox="0 0 200 200">
          <!-- Gauge visualization -->
        </svg>
      </div>
    </section>

    <!-- Critical Metrics Row -->
    <section class="gc-board-metrics" aria-label="Key metrics">
      <article class="gc-board-metric">
        <h3 class="gc-board-metric__title">Frameworks</h3>
        <div class="gc-board-metric__value">
          <span class="gc-board-metric__current">3</span>
          <span class="gc-board-metric__total">/ 5</span>
        </div>
        <div class="gc-board-metric__status gc-board-metric__status--warning">
          At Target
        </div>
      </article>

      <article class="gc-board-metric">
        <h3 class="gc-board-metric__title">Risk Score</h3>
        <div class="gc-board-metric__value">
          <span class="gc-board-metric__score">Medium</span>
        </div>
        <div class="gc-board-metric__status gc-board-metric__status--stable">
          Stable
        </div>
      </article>

      <article class="gc-board-metric">
        <h3 class="gc-board-metric__title">Actions</h3>
        <div class="gc-board-metric__value">
          <span class="gc-board-metric__urgent">2</span>
        </div>
        <div class="gc-board-metric__status gc-board-metric__status--attention">
          Required
        </div>
      </article>

      <article class="gc-board-metric">
        <h3 class="gc-board-metric__title">Investment</h3>
        <div class="gc-board-metric__value">
          <span class="gc-board-metric__amount">$125k</span>
        </div>
        <div class="gc-board-metric__status gc-board-metric__status--info">
          YTD Spend
        </div>
      </article>
    </section>

    <!-- Decisions Section -->
    <section class="gc-board-decisions" aria-label="Decisions required">
      <h2 class="gc-board-decisions__title">Board Decisions Required</h2>

      <div class="gc-board-decision gc-board-decision--urgent">
        <div class="gc-board-decision__content">
          <h3 class="gc-board-decision__title">
            Approve Essential Eight Target
          </h3>
          <p class="gc-board-decision__description">
            Set target maturity level for next 12 months
          </p>
          <div class="gc-board-decision__meta">
            <span class="gc-board-decision__time">3 min</span>
            <span class="gc-board-decision__impact">High Impact</span>
          </div>
        </div>
        <div class="gc-board-decision__actions">
          <button
            class="gc-board-decision__action gc-board-decision__action--primary"
          >
            Review & Decide
          </button>
        </div>
      </div>

      <div class="gc-board-decision">
        <div class="gc-board-decision__content">
          <h3 class="gc-board-decision__title">Cyber Insurance Renewal</h3>
          <p class="gc-board-decision__description">
            Review and approve renewal terms
          </p>
          <div class="gc-board-decision__meta">
            <span class="gc-board-decision__time">5 min</span>
            <span class="gc-board-decision__deadline">Due: March 15</span>
          </div>
        </div>
        <div class="gc-board-decision__actions">
          <button class="gc-board-decision__action">Review</button>
          <button
            class="gc-board-decision__action gc-board-decision__action--defer"
          >
            Defer
          </button>
        </div>
      </div>
    </section>

    <!-- Framework Status Grid -->
    <section class="gc-board-frameworks" aria-label="Framework compliance">
      <h2 class="gc-board-frameworks__title">Compliance Frameworks</h2>

      <div class="gc-board-frameworks__grid">
        <article class="gc-board-framework">
          <header class="gc-board-framework__header">
            <h3 class="gc-board-framework__name">Essential Eight</h3>
            <span
              class="gc-board-framework__badge gc-board-framework__badge--warning"
            >
              ML1
            </span>
          </header>
          <div class="gc-board-framework__progress">
            <div class="gc-board-framework__bar">
              <div class="gc-board-framework__fill" style="width: 40%"></div>
            </div>
            <div class="gc-board-framework__labels">
              <span>Current: 40%</span>
              <span>Target: ML2 (70%)</span>
            </div>
          </div>
        </article>

        <!-- More framework cards -->
      </div>
    </section>

    <!-- Trend Chart -->
    <section class="gc-board-trends" aria-label="Compliance trends">
      <h2 class="gc-board-trends__title">12-Month Trend</h2>
      <div class="gc-board-trends__chart">
        <!-- Simplified line chart -->
        <canvas id="trend-chart" aria-label="Compliance trend chart"></canvas>
      </div>
      <div class="gc-board-trends__legend">
        <span class="gc-board-trends__legend-item">
          <span
            class="gc-board-trends__legend-color"
            style="background: var(--gc-blue-500)"
          ></span>
          Actual
        </span>
        <span class="gc-board-trends__legend-item">
          <span
            class="gc-board-trends__legend-color"
            style="background: var(--gc-gray-400)"
          ></span>
          Target
        </span>
      </div>
    </section>
  </main>
</div>

Responsive Grid Layout

.gc-board-dashboard {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--gc-bg-secondary);
}

.gc-board-header {
  height: 64px;
  background: var(--gc-bg-primary);
  border-bottom: 1px solid var(--gc-border-default);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 24px;
  flex-shrink: 0;
}

.gc-board-main {
  flex: 1;
  padding: 24px;
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: auto auto auto 1fr;
  gap: 24px;
  max-width: 1440px;
  margin: 0 auto;
  width: 100%;
}

/* Hero Section - Full Width */
.gc-board-hero {
  grid-column: 1 / -1;
  background: var(--gc-bg-primary);
  border-radius: 16px;
  padding: 32px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.gc-board-hero__metric {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.gc-board-hero__value {
  font-size: 48px;
  font-weight: 700;
  color: var(--gc-text-primary);
  line-height: 1;
}

.gc-board-hero__label {
  font-size: 18px;
  color: var(--gc-text-secondary);
}

/* Metrics Row */
.gc-board-metrics {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.gc-board-metric {
  background: var(--gc-bg-primary);
  border-radius: 12px;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.gc-board-metric__title {
  font-size: 14px;
  font-weight: 500;
  color: var(--gc-text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.gc-board-metric__value {
  font-size: 32px;
  font-weight: 600;
  color: var(--gc-text-primary);
}

.gc-board-metric__status {
  font-size: 14px;
  font-weight: 500;
  padding: 4px 8px;
  border-radius: 6px;
  align-self: flex-start;
}

.gc-board-metric__status--warning {
  background: var(--gc-amber-100);
  color: var(--gc-amber-700);
}

.gc-board-metric__status--attention {
  background: var(--gc-red-100);
  color: var(--gc-red-700);
}

/* Decisions Section */
.gc-board-decisions {
  grid-column: 1 / 7;
  background: var(--gc-bg-primary);
  border-radius: 12px;
  padding: 24px;
}

.gc-board-decision {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  margin-bottom: 12px;
  border: 2px solid var(--gc-border-default);
  border-radius: 8px;
  transition: all 0.2s ease;
}

.gc-board-decision--urgent {
  border-color: var(--gc-red-300);
  background: var(--gc-red-50);
}

.gc-board-decision:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Frameworks Grid */
.gc-board-frameworks {
  grid-column: 7 / -1;
  background: var(--gc-bg-primary);
  border-radius: 12px;
  padding: 24px;
}

.gc-board-frameworks__grid {
  display: grid;
  gap: 16px;
}

.gc-board-framework {
  padding: 16px;
  border: 1px solid var(--gc-border-light);
  border-radius: 8px;
}

/* Trends Section */
.gc-board-trends {
  grid-column: 1 / -1;
  background: var(--gc-bg-primary);
  border-radius: 12px;
  padding: 24px;
  min-height: 300px;
}

Interactive Features

Quick Actions

class BoardDashboard {
  constructor() {
    this.initQuickActions()
    this.initAutoRefresh()
    this.initKeyboardShortcuts()
  }

  initQuickActions() {
    // One-click decision actions
    document.querySelectorAll('.gc-board-decision__action').forEach((btn) => {
      btn.addEventListener('click', (e) => {
        const decision = e.target.closest('.gc-board-decision')
        const type = e.target.dataset.action || 'review'

        if (type === 'defer') {
          this.deferDecision(decision)
        } else {
          this.openDecisionModal(decision)
        }
      })
    })
  }

  openDecisionModal(decision) {
    // Quick decision interface
    const modal = new DecisionModal({
      title: decision.querySelector('.gc-board-decision__title').textContent,
      timeEstimate: decision.querySelector('.gc-board-decision__time')
        .textContent,
      onComplete: (result) => {
        this.submitDecision(decision, result)
        this.updateDashboard()
      },
    })

    modal.open()
  }

  deferDecision(decision) {
    // Quick defer with reason
    const reason = prompt('Reason for deferral (optional):')

    fetch('/api/decisions/defer', {
      method: 'POST',
      body: JSON.stringify({
        decisionId: decision.dataset.id,
        reason,
        deferUntil: this.getNextQuarter(),
      }),
    }).then(() => {
      decision.classList.add('gc-board-decision--deferred')
      this.showNotification('Decision deferred to next quarter')
    })
  }

  initAutoRefresh() {
    // Refresh data every 5 minutes
    setInterval(
      () => {
        this.refreshMetrics()
      },
      5 * 60 * 1000
    )

    // Visual countdown to next refresh
    this.showRefreshTimer()
  }

  initKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
      // Quick navigation
      if (e.key === '1' && e.ctrlKey) {
        this.focusSection('decisions')
      } else if (e.key === '2' && e.ctrlKey) {
        this.focusSection('frameworks')
      } else if (e.key === 'r' && e.ctrlKey) {
        e.preventDefault()
        this.refreshMetrics()
      } else if (e.key === 'e' && e.ctrlKey) {
        e.preventDefault()
        this.exportReport()
      }
    })
  }
}

Simplified Charts

class SimplifiedChart {
  constructor(canvas, data) {
    this.ctx = canvas.getContext('2d')
    this.data = data
    this.render()
  }

  render() {
    // Simple line chart for trends
    const width = this.ctx.canvas.width
    const height = this.ctx.canvas.height
    const padding = 40

    // Clear canvas
    this.ctx.clearRect(0, 0, width, height)

    // Draw axes
    this.ctx.strokeStyle = '#E5E7EB'
    this.ctx.lineWidth = 1
    this.ctx.beginPath()
    this.ctx.moveTo(padding, padding)
    this.ctx.lineTo(padding, height - padding)
    this.ctx.lineTo(width - padding, height - padding)
    this.ctx.stroke()

    // Draw data line
    this.ctx.strokeStyle = '#3B82F6'
    this.ctx.lineWidth = 2
    this.ctx.beginPath()

    this.data.forEach((point, i) => {
      const x = padding + (i / (this.data.length - 1)) * (width - 2 * padding)
      const y = height - padding - (point.value / 100) * (height - 2 * padding)

      if (i === 0) {
        this.ctx.moveTo(x, y)
      } else {
        this.ctx.lineTo(x, y)
      }

      // Draw point
      this.ctx.fillStyle = '#3B82F6'
      this.ctx.beginPath()
      this.ctx.arc(x, y, 4, 0, Math.PI * 2)
      this.ctx.fill()
    })

    this.ctx.stroke()

    // Draw target line
    this.ctx.strokeStyle = '#9CA3AF'
    this.ctx.lineWidth = 1
    this.ctx.setLineDash([5, 5])
    this.ctx.beginPath()
    const targetY = height - padding - (85 / 100) * (height - 2 * padding)
    this.ctx.moveTo(padding, targetY)
    this.ctx.lineTo(width - padding, targetY)
    this.ctx.stroke()
    this.ctx.setLineDash([])
  }
}

Mobile Optimization

@media (max-width: 1024px) {
  .gc-board-main {
    grid-template-columns: 1fr;
    padding: 16px;
  }

  .gc-board-hero,
  .gc-board-metrics,
  .gc-board-decisions,
  .gc-board-frameworks,
  .gc-board-trends {
    grid-column: 1 / -1;
  }

  .gc-board-hero {
    flex-direction: column;
    text-align: center;
  }

  .gc-board-metrics {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 640px) {
  .gc-board-hero__value {
    font-size: 36px;
  }

  .gc-board-metrics {
    grid-template-columns: 1fr;
  }

  .gc-board-decision {
    flex-direction: column;
    align-items: flex-start;
  }

  .gc-board-decision__actions {
    width: 100%;
    display: flex;
    gap: 8px;
    margin-top: 12px;
  }

  .gc-board-decision__action {
    flex: 1;
  }
}
@media print {
  .gc-board-dashboard {
    height: auto;
  }

  .gc-board-header__actions,
  .gc-board-decision__action--defer {
    display: none;
  }

  .gc-board-main {
    display: block;
    padding: 0;
  }

  .gc-board-hero,
  .gc-board-metrics,
  .gc-board-decisions,
  .gc-board-frameworks,
  .gc-board-trends {
    page-break-inside: avoid;
    margin-bottom: 20px;
  }

  .gc-board-trends__chart {
    height: 200px;
  }
}

Performance Optimization

class DashboardPerformance {
  constructor() {
    this.cache = new Map()
    this.pendingRequests = new Map()
  }

  async fetchMetric(key, url) {
    // Check cache first
    if (this.cache.has(key)) {
      const cached = this.cache.get(key)
      if (Date.now() - cached.timestamp < 60000) {
        // 1 minute cache
        return cached.data
      }
    }

    // Deduplicate requests
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key)
    }

    // Fetch with caching
    const promise = fetch(url)
      .then((r) => r.json())
      .then((data) => {
        this.cache.set(key, {
          data,
          timestamp: Date.now(),
        })
        this.pendingRequests.delete(key)
        return data
      })

    this.pendingRequests.set(key, promise)
    return promise
  }

  async loadDashboard() {
    // Parallel fetch all metrics
    const [compliance, frameworks, decisions, trends] = await Promise.all([
      this.fetchMetric('compliance', '/api/metrics/compliance'),
      this.fetchMetric('frameworks', '/api/metrics/frameworks'),
      this.fetchMetric('decisions', '/api/decisions/pending'),
      this.fetchMetric('trends', '/api/metrics/trends'),
    ])

    return {
      compliance,
      frameworks,
      decisions,
      trends,
    }
  }

  // Progressive enhancement
  enhanceWithDetails() {
    if ('requestIdleCallback' in window) {
      requestIdleCallback(() => {
        this.loadDetailedMetrics()
      })
    } else {
      setTimeout(() => this.loadDetailedMetrics(), 1000)
    }
  }
}

React Implementation

export const BoardDashboard: React.FC = () => {
  const [metrics, setMetrics] = useState(null)
  const [loading, setLoading] = useState(true)
  const [refreshing, setRefreshing] = useState(false)

  // Load initial data
  useEffect(() => {
    loadDashboardData().then((data) => {
      setMetrics(data)
      setLoading(false)
    })
  }, [])

  // Auto-refresh
  useEffect(() => {
    const interval = setInterval(
      () => {
        setRefreshing(true)
        loadDashboardData().then((data) => {
          setMetrics(data)
          setRefreshing(false)
        })
      },
      5 * 60 * 1000
    )

    return () => clearInterval(interval)
  }, [])

  if (loading) {
    return <DashboardSkeleton />
  }

  return (
    <div className="gc-board-dashboard">
      <BoardHeader onExport={exportReport} />

      <main className="gc-board-main">
        <HeroMetric
          value={metrics.overallCompliance}
          trend={metrics.trend}
          benchmark={metrics.industryAverage}
        />

        <MetricsRow metrics={metrics.keyMetrics} />

        <DecisionsPanel
          decisions={metrics.pendingDecisions}
          onDecide={handleDecision}
          onDefer={handleDefer}
        />

        <FrameworksGrid frameworks={metrics.frameworks} />

        <TrendChart data={metrics.trendData} />
      </main>

      {refreshing && <RefreshIndicator />}
    </div>
  )
}

Testing Checklist

  • Dashboard loads in < 2 seconds
  • All information visible without scrolling on 1080p
  • Decisions can be made in < 3 clicks
  • Mobile view maintains all critical information
  • Print version is board-ready
  • Keyboard shortcuts work correctly
  • Auto-refresh doesn't disrupt user actions
  • Color coding is consistent and accessible
  • Export generates PDF in < 5 seconds
  • Works offline with cached data

Conclusion

The one-screen board dashboard delivers maximum insight with minimum cognitive load. By focusing on decisions rather than data, GetCimple enables directors to fulfill their governance obligations efficiently and confidently.