Skip to content

HUD patterns

A HUD lives on one full-viewport, transparent page drawn over gameplay. The examples below use the framework-free classes from @static-bloom/css in the Blackout flavor, over a busy stand-in for game imagery. The Vue package has the same pieces as components: SbHudPanel, SbStatBar, SbKeyHint, SbKbd, and SbToast, whose provider is part of SbProvider.

Vitals

HP72
STA45
ARM12

Objective

Meet the courier at the night market. 1.2 km

FInteractTabScannerRReload
Job accepted
Courier run · 4,500 credits

On a real page the widgets sit in the corners of the viewport with fixed positioning; <html> and <body> stay transparent so the game shows everywhere else. The whole HUD is one page: see Performance.

sb-hud-panel is a glass frame with a tube hairline, corner brackets and a mono title in the accent color. Add sb-scanlines for texture.

<section class="sb-hud-panel sb-scanlines">
<header class="sb-hud-panel-header">
<h3 class="sb-hud-panel-title">Vitals</h3>
<div class="sb-hud-panel-actions">…</div>
</header>
<div class="sb-hud-panel-body">…</div>
<footer class="sb-hud-panel-footer">…</footer>
</section>
Modifier Effect
sb-hud-panel-{secondary,info,success,warning,danger,neutral} Accent of the hairline, brackets and title
sb-hud-panel-compact Tighter padding and smaller brackets
sb-hud-panel-solid Opaque canvas-sunken fill instead of glass, no blur
sb-hud-panel-no-brackets Drops the corner brackets
sb-hud-panel-alarm Pulses the glow; static under reduced motion

sb-stat is a labelled, segmented bar. --sb-value (0 to 100) sets the fill, --sb-segments the number of segments (10 by default). Keep role="meter" and the aria-value* attributes in sync with the value so assistive technology reads it.

<div
class="sb-stat sb-stat-success"
role="meter"
aria-label="Health"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="72"
style="--sb-value: 72"
>
<span class="sb-stat-label">HP</span>
<span class="sb-meter"><span class="sb-meter-fill"></span></span>
<span class="sb-stat-value">72</span>
</div>

Intents (sb-stat-primary, -secondary, -neutral, -info, -success, -warning, -danger) color the fill. sb-stat-critical turns it to danger and pulses it; sb-stat-sm and sb-stat-lg change the size, sb-stat-stacked puts the bar under the label, and sb-meter-continuous drops the segments. The fill animates its width when the value changes.

HEAT64
RAM

4/5

Upload38%

sb-key-hint pairs a keycap with a label. Mark the prompt the player can use right now with data-active, and an unavailable one with aria-disabled="true". Group them in sb-key-hints (or sb-key-hints-vertical).

<div class="sb-key-hints">
<span class="sb-key-hint" data-active
><kbd class="sb-kbd">F</kbd><span class="sb-key-hint-label">Interact</span></span
>
<span class="sb-key-hint" aria-disabled="true"
><kbd class="sb-kbd">R</kbd><span class="sb-key-hint-label">Reload</span></span
>
</div>

Show the key the player actually bound, not a hard-coded default: the client script knows the mapping and can send it with the HUD state.

sb-toast styles a notification: glass, an intent bar on the leading edge, an optional icon, title, description, action and close button. It slides in on mount. Place toasts in a sb-toast-region (-top-right, -bottom-left, -top-center, …), which is fixed to the viewport and stacks them.

<div class="sb-toast-region sb-toast-region-top-right" role="region" aria-label="Notifications">
<div class="sb-toast sb-toast-success" role="status">
<div class="sb-toast-title">Job accepted</div>
<div class="sb-toast-description">Courier run · 4,500 credits</div>
</div>
</div>

Toasts tell the player something without taking focus, so they never need page:setFocus. Keep them short and remove them after a few seconds.

The scene behind a HUD can be anything: a white sky, neon signs, an explosion. Contrast guarantees hold only for solid colors, so:

  • Use Blackout, the flavor with the darkest neutrals.
  • Put every piece of text on a panel: glass for most widgets, sb-hud-panel-solid or sb-card for dense text and over very bright scenes. Never set bare text on the game, which is why the key hints above sit on a glass strip.
  • Keep text at 0.875rem (text-sm) or larger, use the display face for short labels only, and show numbers in the mono face with tabular figures, as sb-stat-value does.
  • Reserve glow for state: a critical stat, an alarm, the active prompt. A glowing HUD has no hierarchy.

Measured over a bright day scene, a night scene and a neon scene, Blackout keeps every HUD text at 4.5:1 or more on the 88% glass. In Midnight, accent-colored titles, values, badges and buttons dip to 4.0:1 over very bright scenes, and in Dusk to 3.3:1; sb-hud-panel-solid brings panel text back above 4.5:1 in both. Daybreak is a website flavor: its HUD panel titles stay near 3.6:1 even on a solid panel.

Glass

Base at 88% with a 12px blur. For most widgets.

Solid

Opaque canvas-sunken. For long text and bright scenes.
  • One page for the HUD. Every WebUI page is a separate browser with its own GPU memory, and a client allows 32 pages for all of a server’s resources. Stat bars, key hints, toasts and the minimap frame belong on the same page (budget).
  • Keep it still when nothing changes. A page repaints for every animation frame. Use the looping effects (sb-hud-panel-alarm, sb-stat-critical, animate-pulse-glow, animate-flicker) for real alerts and remove them when the alert ends. Pages render at 30 frames per second unless WebUI.create sets another fps.
  • Few glass panels. backdrop-filter blurs the game behind the panel on every repaint. A handful of small panels is fine; for a large or always-visible surface, prefer sb-hud-panel-solid.
  • Animate cheap properties. Transitions on transform and opacity stay on the compositor; the stat fill animates its width only when the value changes.
  • Batch state. Send one hud:state event with every value rather than one event per widget, and update the DOM once per event.