Skip to content

Container card

Source: dockview/public/app.js — static DOM rendered from /api/containers response Category: Composite component

Container card — the main dockview dashboard unit. Shows a single Docker container’s state, resource usage, and actions (start / stop / restart / logs). Multiple cards in a responsive grid.

A grid of cards. Each card: colored state dot, container name, image tag, uptime, CPU/memory meters, action buttons. The real dockview renders these with plain DOM; the wiki demo uses React for the same shape.

portfolio-web8d 4h
node:22-alpine
cpu
6%
mem
12%
atrium-backend2h 14m
node:20
cpu
12%
mem
22%
immich-server14d
immich:v1.160
cpu
48%
mem
56%
old-artifex
artifex:prev
cpu
0%
mem
0%
function renderContainer(c) {
const el = document.createElement('div');
el.className = `card state-${c.state}`;
el.dataset.id = c.id;
el.innerHTML = `
<header>
<span class="dot"></span>
<span class="name">${escape(c.name)}</span>
<span class="uptime">${c.uptime}</span>
</header>
<code class="image">${escape(c.image)}</code>
<div class="meter cpu"><div class="fill" style="width:${c.cpu}%"></div></div>
<div class="meter mem"><div class="fill" style="width:${c.mem.pct}%"></div></div>
<footer>
<button data-action="start">Start</button>
<button data-action="stop">Stop</button>
<button data-action="restart">Restart</button>
<button data-action="logs">Logs</button>
</footer>
`;
return el;
}
// Event delegation at the parent — one listener, any number of cards
document.getElementById('containers').addEventListener('click', (e) => {
const button = e.target.closest('button');
const card = e.target.closest('.card');
if (!button || !card) return;
const action = button.dataset.action;
const id = card.dataset.id;
handleAction(action, id);
});
  • State dot with subtle glow. box-shadow: 0 0 6px <color> on running. Silent signal; no text needed.
  • Meters, not numbers alone. Visual bars are scannable across 10+ cards; “87%” requires reading.
  • Uptime top-right. Second most important value after state; sits in the corner where eyes land next.
  • Action row at the bottom. Constant action surface means users don’t have to hunt per card.
  • Image tag in monospace. It’s an identifier; monospace reads as one.
  • dockview — the whole dashboard is a grid of these
  • Pattern generalizes to any “grid of status + actions” UI: pipelines, services, workers, workflow runs
  • State-transition animation. Going from “stopped” (gray) to “running” (green with glow) should be a 200ms color fade, not instant. Helps the eye follow.
  • Actions should be optimistic. Click “Stop”, card immediately shows the “stopping” state; rollback if the API rejects.
  • Don’t render meters when stopped. 0% meters everywhere look noisy. Replace with ”—” text.
  • Long names truncate with ellipsis. white-space: nowrap; overflow: hidden; text-overflow: ellipsis; plus min-width: 0 on the flex child.
  • Responsive grid. grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)) — one line, full responsive behavior.
  • Update granularity. Full card re-render on every metric tick is wasteful for 20+ containers. Update only the changed data-attributes (data-cpu, data-mem) and have CSS drive the visual.
  • Destructive actions need confirmation. “Stop” on a load-bearing container can ruin a day; confirm or add an undo.
  • Logs open in a modal or new tab. Inline logs on a card are both small and long — pick the wrong problem to solve.